part | letter |
---|---|
1 |
a |
You may use the new generation frontend tool Vite or the default tool create-react-app to get started.
npx create-react-app part1
cd part1
npm start
The applicaiton runs on http://localhost:3000
The file index.js looks like this
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
and file App.js looks like this
const App = () => (
<div>
<p>Hello world</p>
</div>
);
export default App;
The file public/index.js looks like this :
<!DOCTYPE html>
<html lang="en">
<head>
content not shown ...
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
There are a few ways to define functions in JavaScript. Here we will use arrow functions, which are described in a newer version of JavaScript known as ECMAScript 6, also called ES6.
The first rule of frontend web development:
keep the console open all the time
Rendering a dynamic content inside of a component:
const App = () => {
const now = new Date();
return (
<div>
<p>Hello world, it is {now.toString()}</p>
</div>
);
};
export default App;
The layout of React components is mostly written using JSX. Although JSX looks like HTML, we are dealing with a way to write JavaScript. Under the hood, JSX returned by React components is compiled into JavaScript.
const App = () => {
const now = new Date();
return React.createElement(
'div',
null,
React.createElement('p', null, 'Hello world, it is ', now.toString())
);
};
The compilation is handled by Babel. JSX is "XML-like", which means that every tag needs to be closed. For example, a newline is an empty element, which in HTML can be written as follows:
<br />
but when writing JSX, the tag needs to be closed:
<br />
A core philosophy of React is composing applications from many specialized reusable components.
Another strong convention is the idea of a root component called App at the top of the component tree of the application. Nevertheless, as we will learn in part 6, there are situations where the component App is not exactly the root, but is wrapped within an appropriate utility component.
It is possible to pass data to components using so-called props.
const Hello = ({ name, age }) => {
// highlight-line
return (
<div>
Hello {name}, you are {age} years old // highlight-line
</div>
);
};
The props are defined as follows:
const App = () => {
const name = 'Peter'; // highlight-line
const age = 10; // highlight-line
return (
<div>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} /> // highlight-line
<Hello name={name} age={age} /> // highlight-line
</div>
);
};
Keep in mind that React component names must be capitalized.
Note that the content of a React component (usually) needs to contain one root element.
Using a root element is not the only working option. An array of components is also a valid solution:
const App = () => {
return [<h1>Greetings</h1>, <Hello name="Maya" age={26 + 10} />, <Footer />];
};
However, when defining the root component of the application this is not a particularly wise thing to do, and it makes the code look a bit ugly.
Because the root element is stipulated, we have "extra" div elements in the DOM tree. This can be avoided by using fragments, i.e. by wrapping the elements to be returned by the component with an empty element:
const App = () => {
const name = 'Peter';
const age = 10;
return (
<>
<h1>Greetings</h1>
<Hello name="Maya" age={26 + 10} />
<Hello name={name} age={age} />
<Footer />
</>
);
};
It now compiles successfully, and the DOM generated by React no longer contains the extra div element.
Consider an application that prints the names and ages of our friends on the screen:
const App = () => {
const friends = [
{ name: 'Peter', age: 4 },
{ name: 'Maya', age: 10 },
];
return (
<div>
<p>{friends[0]}</p>
<p>{friends[1]}</p>
</div>
);
};
export default App;
The core of the problem is Objects are not valid as a React child, i.e. the application tries to render objects and it fails again.
In React, the individual things rendered in braces must be primitive values, such as numbers or strings.
The fix is ​​as follows
....
return (
<div>
<p>
{friends[0].name} {friends[0].age}
</p>
<p>
{friends[1].name} {friends[1].age}
</p>
</div>
);
....
React also allows arrays to be rendered if the array contains values ​​that are eligible for rendering (such as numbers or strings).
Famous software developer Robert "Uncle Bob" Martin has stated
"The only way to go fast, is to go well"
that is, according to Martin, careful progress with small steps is even the only way to be fast.
WARNING2 create-react-app automatically makes the project a git repository unless the application is created within an already existing repository. Most likely you do not want the project to become a repository, so run the command rm -rf .git in the root of the project.
The official name of the JavaScript standard is ECMAScript. At this moment, the latest version is the one released in June of 2022 with the name ECMAScript®2022, otherwise known as ES13.
Browsers do not yet support all of JavaScript's newest features. Due to this fact, a lot of code run in browsers has been transpiled from a newer version of JavaScript to an older, more compatible version.
Today, the most popular way to do transpiling is by using Babel.
Node.js is a JavaScript runtime environment based on Google's Chrome V8 JavaScript engine and works practically anywhere - from servers to mobile phones. Let's practice writing some JavaScript using Node. The latest versions of Node already understand the latest versions of JavaScript, so the code does not need to be transpiled.
Where to run Node.js:
1.JS Bin.
2.the browser's developer tool console
3.Node.js console, which is opened by typing node in the command line
const x = 1;
let y = 5;
console.log(x, y); // 1, 5 are printed
y += 10;
console.log(x, y); // 1, 15 are printed
y = 'sometext';
console.log(x, y); // 1, sometext are printed
x = 4; // causes an error
The variable's data type can change during execution
var. See JavaScript Variables - Should You Use let, var or const? on Medium or Keyword: var vs. let on JS Tips or watch var, let and const - ES6 JavaScript Features for more information.
const t = [1, -1, 3];
t.push(5);
console.log(t.length); // 4 is printed
console.log(t[1]); // -1 is printed
t.forEach((value) => {
console.log(value); // numbers 1, -1, 3, 5 are printed, each to own line
});
Notable in this example is the fact that the contents of the array can be modified even though it is defined as a const. Because the array is an object, the variable always points to the same object. However, the content of the array changes as new items are added to it.
forEach calls the function for each of the items in the array, always passing the individual item as an argument. The function as the argument of forEach may also receive other arguments.
In the previous example, a new item was added to the array using the method push.
When using React, techniques from functional programming are often used. One characteristic of the functional programming paradigm is the use of immutable data structures. In React code, it is preferable to use the method concat, which creates a new array with the added item. This ensures the original array remains unchanged.
const m1 = t.map((value) => value * 2);
Based on the old array, map creates a new array, for which the function given as a parameter is used to create the items.
const m2 = t.map((value) => '<li>' + value + '</li>');
console.log(m2);
// [ '<li>1</li>', '<li>2</li>', '<li>3</li>' ] is printed
const t = [1, 2, 3, 4, 5];
const [first, second, ...rest] = t;
console.log(first, second); // 1, 2 is printed
console.log(rest); // [3, 4, 5] is printed
const object3 = {
name: {
first: 'Dan',
last: 'Abramov',
},
grades: [2, 3, 5, 3],
department: 'Stanford University',
};
console.log(object1.name); // Arto Hellas is printed with dot notations
const fieldName = 'age';
console.log(object1[fieldName]); // 35 is printed with brackets []
object1.address = 'Helsinki';
object1['secret number'] = 12341;
The latter of the additions has to be done by using brackets because of the space. Naturally, objects in JavaScript can also have methods.
function product(a, b) {
return a * b;
}
const result = product(2, 6);
// result is now 12
The other way to define the function is by using a function expression. In this case, there is no need to give the function a name and the definition may reside among the rest of the code:
const average = function (a, b) {
return (a + b) / 2;
};
const result = average(2, 5);
// result is now 3.5
During this course, we will define all functions using the arrow syntax.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log('hello, my name is ' + this.name);
}
}
const adam = new Person('Adam Ondra', 29);
adam.greet();
const janja = new Person('Janja Garnbret', 23);
janja.greet();
At the core, they are still objects based on JavaScript's prototypal inheritance. The type of both objects is actually Object, since JavaScript essentially only defines the types Boolean, Null, Undefined, Number, String, Symbol, BigInt, and Object.
The introduction of the class syntax was a controversial addition. Check out Not Awesome: ES6 Classes or Is “Class” In ES6 The New “Bad” Part? on Medium.
There exist both good and poor guides for JavaScript on the Internet. Most of the links on this page relating to JavaScript features reference Mozilla's JavaScript Guide.
It is highly recommended to immediately read A re-introduction to JavaScript (JS tutorial) on Mozilla's website.
If you wish to get to know JavaScript deeply there is a great free book series on the Internet called You-Dont-Know-JS.
Another great resource for learning JavaScript is javascript.info.
The free and highly engaging book Eloquent JavaScript takes you from the basics to interesting stuff quickly. It is a mixture of theory projects and exercises and covers general programming theory as well as the JavaScript language.
Namaste 🙏 JavaScript is another great and highly recommended free JavaScript tutorial in order to understand how JS works under the hood. Namaste JavaScript is a pure in-depth JavaScript course released for free on YouTube. It will cover the core concepts of JavaScript in detail and everything about how JS works behind the scenes inside the JavaScript engine.
egghead.io has plenty of quality screencasts on JavaScript, React, and other interesting topics. Unfortunately, some of the material is behind a paywall.
allows us to destructure values from objects and arrays upon assignment.
Counter with props:
export default App = ({ counter }) => <div>{counter}</div>;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
let counter = 1;
const refresh = () => {
ReactDOM.createRoot(document.getElementById('root')).render(
<App counter={counter} />
);
};
setInterval(() => {
refresh();
counter += 1;
}, 1000);
import { useState } from 'react'; // highlight-line
const App = () => {
const [counter, setCounter] = useState(0); // highlight-line
// highlight-start
setTimeout(() => setCounter(counter + 1), 1000);
// highlight-end
return <div>{counter}</div>;
};
export default App;
The function call adds state to the component and renders it initialized with the value of zero. The function returns an array that contains two items. We assign the items to the variables counter and setCounter by using the destructuring assignment syntax shown earlier.
The counter variable is assigned the initial value of state which is zero. The variable setCounter is assigned a function that will be used to modify the state.
The application calls the setTimeout function and passes it two parameters: a function to increment the counter state and a timeout of one second.
The function passed as the first parameter to the setTimeout function is invoked one second after calling the setTimeout function
When the state modifying function setCounter is called, React re-renders the component which means that the function body of the component function gets re-executed:
The second time the component function is executed it calls the useState function and returns the new value of the state: 1. Executing the function body again also makes a new function call to setTimeout, which executes the one-second timeout and increments the counter state again. Because the value of the counter variable is 1, incrementing the value by 1 is essentially the same as an expression setting the value of counter to 2.
Every time the setCounter modifies the state it causes the component to re-render. The value of the state will be incremented again after one second, and this will continue to repeat for as long as the application is running.
const App = () => {
const [counter, setCounter] = useState(0);
// highlight-start
const handleClick = () => {
console.log('clicked');
};
// highlight-end
return (
<div>
<div>{counter}</div>
// highlight-start
<button onClick={handleClick}>plus</button>
// highlight-end
</div>
);
};
An event handler is supposed to be either a function or a function reference, and when we write:
<button onClick={setCounter(counter + 1)}>
the event handler is actually a function call. In many situations this is ok, but not in this particular situation. In the beginning, the value of the counter variable is 0. When React renders the component for the first time, it executes the function call setCounter(0+1), and changes the value of the component's state to 1. This will cause the component to be re-rendered, React will execute the setCounter function call again, and the state will change leading to another rerender...
Usually defining event handlers within JSX-templates is not a good idea.
It's recommended to write React components that are small and reusable across the application and even across projects.
One best practice in React is to lift the state up in the component hierarchy. The documentation says:
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
const App = () => {
const [counter, setCounter] = useState(0);
const increaseByOne = () => setCounter(counter + 1);
//highlight-start
const decreaseByOne = () => setCounter(counter - 1);
//highlight-end
const setToZero = () => setCounter(0);
return (
<div>
<Display counter={counter} />
// highlight-start
<Button handleClick={increaseByOne} text="plus" />
<Button handleClick={setToZero} text="zero" />
<Button handleClick={decreaseByOne} text="minus" />
// highlight-end
</div>
);
};
How application works:
When the application starts, the code in App is executed. This code uses a useState hook to create the application state, setting an initial value of the variable counter. This component contains the Display component - which displays the counter's value, 0 - and three Button components. The buttons all have event handlers, which are used to change the state of the counter.
When one of the buttons is clicked, the event handler is executed. The event handler changes the state of the App component with the setCounter function. Calling a function that changes the state causes the component to rerender. This causes its subcomponents Display and Button to also be re-rendered.
const App = () => {
const [clicks, setClicks] = useState({
left: 0,
right: 0,
});
const handleLeftClick = () => {
const newClicks = {
left: clicks.left + 1,
right: clicks.right,
};
setClicks(newClicks);
};
const handleRightClick = () => {
const newClicks = {
left: clicks.left,
right: clicks.right + 1,
};
setClicks(newClicks);
};
return (
<div>
{clicks.left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{clicks.right}
</div>
);
};
We can define the new state object a bit more neatly by using the object spread syntax that was added to the language specification in the summer of 2018:
const handleLeftClick = () => setClicks({ ...clicks, left: clicks.left + 1 });
const handleRightClick = () =>
setClicks({ ...clicks, right: clicks.right + 1 });
The application appears to work. However, it is forbidden in React to mutate state directly, since it can result in unexpected side effects.
Storing all of the state in a single state object is a bad choice for this particular application; There are situations where it can be beneficial to store a piece of application state in a more complex data structure. The official React documentation contains some helpful guidance on the topic.
const App = () => {
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
const [allClicks, setAll] = useState([]); // highlight-line
// highlight-start
const handleLeftClick = () => {
setAll(allClicks.concat('L'));
setLeft(left + 1);
};
// highlight-end
// highlight-start
const handleRightClick = () => {
setAll(allClicks.concat('R'));
setRight(right + 1);
};
// highlight-end
return (
<div>
{left}
<button onClick={handleLeftClick}>left</button>
<button onClick={handleRightClick}>right</button>
{right}
<p>{allClicks.join(' ')}</p> // highlight-line
</div>
);
};
Adding the new item to the array is accomplished with the concat method, which does not mutate the existing array but rather returns a new copy of the array with the item added to it.
A state update in React happens asynchronously, i.e. not immediately but "at some point" before the component is rendered again.
We can fix the app as follows:
const App = () => {
// ...
const handleLeftClick = () => {
setAll(allClicks.concat('L'));
const updatedLeft = left + 1;
setLeft(updatedLeft);
setTotal(updatedLeft + right);
};
// ...
};
The History component renders completely different React elements depending on the state of the application. This is called conditional rendering.
React also offers many other ways of doing conditional rendering.
const History = (props) => {
if (props.allClicks.length === 0) {
return <div>the app is used by pressing the buttons</div>;
}
return <div>button press history: {props.allClicks.join(' ')}</div>;
};
// highlight-start
const Button = ({ handleClick, text }) => (
<button onClick={handleClick}>{text}</button>
);
// highlight-end
const App = () => {
const [left, setLeft] = useState(0);
const [right, setRight] = useState(0);
const [allClicks, setAll] = useState([]);
const handleLeftClick = () => {
setAll(allClicks.concat('L'));
setLeft(left + 1);
};
const handleRightClick = () => {
setAll(allClicks.concat('R'));
setRight(right + 1);
};
return (
<div>
{left}
// highlight-start
<Button handleClick={handleLeftClick} text="left" />
<Button handleClick={handleRightClick} text="right" />
// highlight-end
{right}
<History allClicks={allClicks} />
</div>
);
};
A large part of a typical developer's time is spent on debugging and reading existing code. Every now and then we do get to write a line or two of new code, but a large part of our time is spent trying to figure out why something is broken or how something works. Good practices and tools for debugging are extremely important for this reason.
NB When you use console.log for debugging, don't combine objects in a Java-like fashion by using the plus operator:
console.log('props value is ' + props);
If you do that, you will end up with a rather uninformative log message:
props value is [object Object]
Instead, separate the things you want to log to the console with a comma:
console.log('props value is', props);
In this way, the separated items will all be available in the browser console for further inspection.
You can pause the execution of your application code in the Chrome developer console's debugger, by writing the command debugger anywhere in your code.
The execution will pause once it arrives at a point where the debugger command gets executed.
You can also access the debugger without the debugger command by adding breakpoints in the Sources tab. Inspecting the values of the component's variables can be done in the Scope-section:
It is highly recommended to add the React developer tools extension to Chrome. It adds a new Components tab to the developer tools. The new developer tools tab can be used to inspect the different React elements in the application, along with their state and props:
The useState function (as well as the useEffect function introduced later on in the course) must not be called from inside of a loop, a conditional expression, or any place that is not a function defining a component. This must be done to ensure that the hooks are always called in the same order, and if this isn't the case the application will behave erratically.
To recap, hooks may only be called from the inside of a function body that defines a React component:
<button onClick={console.log('clicked the button')}>button</button>
The message gets printed to the console once when the component is rendered but nothing happens when we click the button. Why does this not work even when our event handler contains a function console.log?
The issue here is that our event handler is defined as a function call which means that the event handler is assigned the returned value from the function, which in the case of console.log is undefined.
The console.log function call gets executed when the component is rendered and for this reason, it gets printed once to the console.
const App = () => {
const [value, setValue] = useState(10);
// highlight-start
const hello = (who) => () => {
console.log('hello', who);
};
// highlight-end
return (
<div>
{value}
<button onClick={hello()}>button</button>
</div>
);
};
We can use the same trick to define event handlers that set the state of the component to a given value. Let's make the following changes to our code:
const App = () => {
const [value, setValue] = useState(10);
// highlight-start
const setToValue = (newValue) => () => {
console.log('value now', newValue); // print the new value to console
setValue(newValue);
};
// highlight-end
return (
<div>
{value}
// highlight-start
<button onClick={setToValue(1000)}>thousand</button>
<button onClick={setToValue(0)}>reset</button>
<button onClick={setToValue(value + 1)}>increment</button>
// highlight-end
</div>
);
};
Using functions that return functions is not required to achieve this functionality. Let's return the setToValue function which is responsible for updating state into a normal function:
const App = () => {
const [value, setValue] = useState(10);
const setToValue = (newValue) => {
console.log('value now', newValue);
setValue(newValue);
};
return (
<div>
{value}
<button onClick={() => setToValue(1000)}>thousand</button>
<button onClick={() => setToValue(0)}>reset</button>
<button onClick={() => setToValue(value + 1)}>increment</button>
</div>
);
};
Choosing between the two presented ways of defining your event handlers is mostly a matter of taste.
Easy
Never define components inside of other components. The method provides no benefits and leads to many unpleasant problems. The biggest problems are because React treats a component defined inside of another component as a new component in every render. This makes it impossible for React to optimize the component.
The internet is full of React-related material. However, we use the new style of React for
- The official React documentation is worth checking out at some point, although most of it will become relevant only later on in the course. Also, everything related to class-based components is irrelevant to us;
- Some courses on Egghead.io like Start learning React are of high quality, and the recently updated Beginner's Guide to React is also relatively good; both courses introduce concepts that will also be introduced later on in this course. NB The first one uses class components but the latter uses the new functional ones.
How to Round: Math.round() to round to the nearest integer. parseInt() to parse to the nearest whole number./Slowest Math.trunc() to completely strip the decimal. toFixed() to remove a certain amount of decimals.
How to create a zero filled JavaScript array of arbitrary length?