Slide 1
-----------
Part 1: Let's finally have som fun and start building truly reactive React components!
Slide 2
-----------
Part 1: In React, "reactivity" refers to the way in which React applications respond to changes in state or props by automatically updating the user interface (UI). This concept is central to React’s design and helps make it a powerful library for building dynamic web applications.
Part 2: Thus, instead of telling the webpage how to change the website after the state has changed, we describe how the page should look based on the current state and let React take care of the changes.
Part 3: All we need to do is tell react when to initiate the state change. In our case, we change the state when clicking the button. After every click, the state changes and a new count is rendered. Let's explore more!
Slide 3
-----------
Part 1: While React also supports the definition of Components using classes ...
Part 2: ... we will focus on a more modern representation of React components using functions, also known as functional components. In React, functional components use special functions called hooks to control the life cycle and behaviour of react components.
Part 3: You can easily spot the react hook as it will have the “use” prefix.
Part 4: For example, we will use useState hook to control the state and reactivity of the component ...
Part 5: useEffect to control the side effects and clean up after the component
Part 6: useMemo to remember values of expensive computations and many others. This section will focus on the useState hook to control the reactivity of functional React components.
Slide 4
-----------
Part 1: Let's revisit the previous example and discuss React state in detail! State in React determines the behaviour and rendering of components. When state data changes, React re-renders the component to reflect the updates. Try clicking the plus button to see how the component automatically renders a new value.
Part 2: In functional components, we will use the useState hook from the React library to control the component state.
Part 3: The useState hooks return an array of two elements.
Part 4: The first array element is the value from the state ...
Part 5: ... and the second one is a function we use to change the value.
Part 6: We use array destructuring to assign values to variables.
Slide 5
-----------
Part 1: To manage the state, we often define custom handlers, such as this inc function, which increases the state value by one.
Part 2: We can then show the current value in a position where we desire, with its own custom look and styles.
Part 3: We also assign our custom state change handler to the button's onClick function. Therefore, every time we click the button, it will call the inc function, which will change the state.
Part 4: The last thing we need to mention is that the state change function can also accept a producer function that takes one parameter, a current value of the state variable and returns a new state. This is often useful when dealing with asynchronous state changes, ensuring the state never becomes stale.
Slide 6
-----------
Part 1: Let's take a look at a more complex example, where we store arrays in the React state. Similar to our previous example, we initialise the state and state change function, this time storing the array value.
Part 2: In the state change handler function, we simply push a new string to the state array and store the same array in the state.
Part 3: Then, we wire our state change handler to the button click ...
Part 4: And show all the array content, separated by a comma. But when you click on the button, nothing happens! When dealing with React state, the most important thing to realise is that you must provide a different state value to re-render the component. Otherwise, no change will happen. This is simple with atomic values such as string or number but becomes more complicated with objects or arrays, which use object references. React makes only object reference comparisons for performance reasons.
Part 5: Therefore, to change the state, it is not sufficient to update the array. You have to create a new array to create a new object reference. In this case, we use the convenient spread operator to change the array.
Part 6: When you click on the plus button now, all is well as react recognises that a state has been updated.
Part 7: If you do not like the spread operator, you can use the concat function to create a new array combining two provided array. So, please remember that functions such as push, pop, shift or unshift that modify existing array in-place will not work for React state changes and a new object reference to a modified array must be provided.
Slide 7
-----------
Part 1: With objects, situation is the same as with arrays. Changing object properties does not change the object reference and you have to update the state with a new object value. Consider this example.
Part 2: First, we initialise the state that is an object value, holding name and age of a person.
Part 3: Then, we define our state handler where we increase the age of the person and store it back to the state. Can you already spot the error?
Part 4: Then, we show the name and age inside a div.
Part 5: Last, we wire the button click to the state change handler. But when we click the button, the person's age does not update! Let's fix it.
Part 6: Let's fix it! The goal is to produce a new state, in our case a new object. We will use the convenient spread operator to create the new state. Similarly you could use the assign function to create a new object but this will do!
Part 7: When we run our example now, all works as expected!
Slide 8
-----------
Part 1: Sometimes, you may want to use the same component state repeatedly in your app across many components.
Part 2: For example, you want to track and modify this user data across more components in your app.
Part 3: For such purpose, we create a new usePersonData to track user data and we also add some extra functionality to simplify their use.
Part 4: For example, we will initialise user data with some default values and allow to specify only a subset of required values.
Part 5: We also add a new method that will allow to specify only a subset of user data that we want to update, simplifying data updates.
Part 6: Now, we are ready to use this hook. Note that we use object destructuring and not array as in regular hooks, as we return an object from our custom hook, not an array.
Part 7: We can now use our fancy new method to use partial data to update the state, reducing the need for spread operators.
Part 8: We can use the data as with a regular hook. Pretty cool, huh? Online, you may find collections of custom hooks simplifying common operations, and reducing the boilerplate in your applications. Let's take a look now at some limits of hooks and the rules we need to follow when using them,
Slide 9
-----------
Part 1: When using hooks you need to remember two important rules. Rule #1: Hooks can only be called from within the functional components.
Part 2: For example, the following code would fail and rendering this component would lead to runtime warning telling you that hooks can only be called inside the body of a function.
Slide 10
-----------
Part 1: To fix it, you must move hooks inside the component and the code will work.
Slide 11
-----------
Part 1: If you paid attention, you might argue that custom hooks are not components, yet we use hooks inside them!
Part 2: Well, even though the custom hook is NOT a component, we are calling it from within the component. That is why we are not violating the number one rule of hooks. React knows, that the hook was called from within a component.
Slide 12
-----------
Part 1: Rule #2 dictates that you must render the same number of hooks on each component render. Ergo, you cannot conditionally call hooks.
Part 2: In this example, the first click of the button works just fine, but clicking the button a second time crashes the application.
Part 3: The reason for this is that when the value of the count reaches one, we initialise a new state variable that breaks the rule of hooks.
Slide 13
-----------
Part 1: The way to fix this is to extract the conditional code into a custom component and track its state separately.
Slide 14
-----------
Part 1: In React, state updates are batched to optimize performance and minimize unnecessary re-renders. Batching means that multiple state updates within the same event are combined into a single re-render. LEt's take a look at the example.
Part 2: We print the message that the component is rendering.
Part 3: The console.log prints the stale state (values before the update).
Part 4: setCount1 and setCount2 are called sequentially. React batches both updates and processes them together in one re-render.
Part 5: Then, we log the count values and observe their value. Do you think the value changed?
Part 6: Let's take a look at a console, what we see there once we click the button. The rendering happens only once! The updates happened in batch!
Part 7: The Before Updates log will show the old values.
Part 8: The After Updates log will still show stale values because state updates are asynchronous.
REACT = REACTIVITY
[https://skillpies.s3.ap-southeast-2.amazonaws.com/courses/3myFj3C3s45Lw7am7p/sections/830NsKij59BimhIzTj/reactivity-featured.png%20copy.jpg]
In React, "reactivity" refers to the way in which React applications respond to
changes in state or props by automatically updating the user interface (UI).
This concept is central to React’s design and helps make it a powerful library
for building dynamic web applications. Thus, instead of telling the webpage how
to change the website after the state has changed, we describe how the page
should look and let React take care of the changes.
The primary tool for reactivity in React is the state. Managing state in React
involves understanding how to efficiently handle operations like adding,
removing, and updating items within arrays. This section explores these
fundamental concepts using the React state management system.
🪝HOOKS
While React also supports the definition of Components using classes, we will
focus on a more modern representation of React components using functions, also
known as functional components. In React, functional components use special
functions called hooks to control the life cycle and behaviour of react
components. You can easily spot the react hook as it will have the “use” prefix.
For example, we will use:
* useState hook to control the state and reactivity of the component
* useEffect to control the side effects and clean up after the component
* useMemo to remember values of expensive computations
* … and others
In this section, we focus on the most use hook of them all “useState”
INTRODUCTION TO STATE IN REACT
State in React is a set of data that determines the behaviour and rendering of
components. When state data changes, React re-renders the component to reflect
the updates. The state is typically initialised in a component's constructor
(for class components) or using the useState hook in functional components.
Below is an example of using state in a functional component:
function Counter() {
const [value, setValue] = useState(0);
function inc() {
setValue(value + 1);
}
return (
<div>
Counter: {value}
<button onClick={}>+</button>
</div>
)
}
The useState hooks return an array of with two elements. First array element is
the value from the state and the second one is a function we use to change the
value. We use array destructuring to assign values into variable.
Furthermore, in our example, the button increases the count value. Without
telling React how to update the value on the screen the counter value is
increased. Magic! 🪄
PRODUCER FUNCTIONS
The setValue in the example above either accepts a new value or a producer
function. Producer functions assure that the value used to compute the new state
is never stale. This can often happen when using custom hooks, useMemo hook or
asynchronous operations. For example:
function Counter() {
const [value, setValue] = useState(0);
function inc() {
// producer function
setValue((value) => value + 1);
}
return (
<div>
Counter: {value}
<button onClick={}>+</button>
</div>
)
}
We will show some examples of stale state once we start exploring the useMemo
hook.
STATE WITH COMPLEX VALUES
Here, the things get tricky. The most important thing to realise is that you
have to provide a different value in order to re-render the component, otherwise
no change will happen. This is simple with atomic values such as string or
number, but becomes more complicated with objects or arrays, which use object
references. React does only shallow (object reference) comparison for
performance reasons.
ARRAYS
To change the state it is not sufficient to update the array. You have to create
a new array to create a new object reference. In the following example, pressing
the “+” button does nothing.
function NameList() {
const [names, setNames] = useState(["Tomas"]);
function insertName() {
names.push("Valeria");
setNames(names)
}
return (
<div>
{names.join(", ")}
<Button onClick={insertName}>+</Button>
</div>
)
}
We have modified only the internal properties and structure of the array,
retaining the same object reference. Similarly doing any of the operations on
the array will not produce any state change:
* push
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push]
* pop
* shift
* unshift
* splice
* reverse
Instead, we need to use methods that modify the array and return a nbew object
reference such as:
* concat
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat]
* slice
[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice]
* … spread operator
Let's fix the example above in a couple of ways.
function NameList() {
const [names, setNames] = useState(["Tomas"]);
function insertName() {
// fix 1
setNames(names.concat(["Valeria"]))
// or, fix 2
setNames([...names, "Valeria" ])
}
return (
<div>
{names.join(", ")}
<Button onClick={insertName}>+</Button>
</div>
)
}
OBJECTS
With objects, situation is the same as with arrays. Changing object properties
does not change the object reference and you have to update the state with a new
object value. Consider the following failing example:
function NameList() {
const [person, setPerson] = useState({ name: "Tomas", age: 44 });
function increaseAge() {
person.age += 1;
setPerson(person);
}
return (
<div>
Name: { person.name }<br />
Age: { person.age }
<Button onClick={increaseAge}>+</Button>
</div>
)
}
Before you look at the solution below, try to fix the example yourself.! Did you
do it? If not, check out our solution, we fix it in two different ways, using a
spread operator and using Object.assign function.
function NameList() {
const [person, setPerson] = useState({ name: "Tomas", age: 44 });
function increaseAge() {
// fix 1
setPerson(Object.assign(person, { age: person.age + 1});
// or fix 2
setPerson({
...person,
age: person.age + 1
})
}
return (
<div>
Name: { person.name }<br />
Age: { person.age }
<Button onClick={increaseAge}>+</Button>
</div>
)
}
While both ways do the same, it is better to use the spread operator as it is
easier to read and works seamlessly for complex components. In the next section,
let's work on an exercise using useState.
🪝 CUSTOM HOOKS
Often, you keep using the same component state repeatedly in your app across
many component. Or, you mimic the same component behaviour across your app. For
such purpose, you can create a custom hook. For example, imagine that you track
person data in many components, and you want to only use partial data to
initialise the person information. For such functionality, we add a
usePersonData hook:
function usePersonData(partialPerson: Partial<Person>) {
const [person, setPerson] = useState<Person>({
name: "John",
lastName: "Doe",
age: 0,
...partialPerson
})
return {
person,
setPerson,
setPartialPerson(partialPerson: Partial<Person>) {
setPerson({ ...person, ...partialPerson })
}
}
}
function NameList() {
const { person, setPartialPerson } = usePersonData({ name: "Tomas", age: 44 });
function increaseAge() {
setPartialPerson({
// ...person // no need for this!
age: person.age + 1
})
}
return (
<div>
Name: { person.name }<br />
Age: { person.age }
<Button onClick={increaseAge}>+</Button>
</div>
)
}
As you can see, cusom hook can help create helper functions simplifying the
overall complexity of your application code.
🪝 RULES OF HOOKS
When using hooks you need to remember two important rules:
RULE #1: HOOKS CAN ONLY BE CALLED FROM WITHIN THE FUNCTIONAL COMPONENTS.
For example, the following would fail:
const [count, setCount] = useState(0)
function App() {
return <div>{count}</div>
}
Rendering this component would lead to runtime error. To fix it, you must move
hooks inside the component:
function App() {
const [count, setCount] = useState(0)
return <div>{count}</div>
}
But, you may argue that custom hooks are NOT functions, yet we use react hooks
within! For example:
function useCounter() {
const [count, setCount] = useState(0)
return { count, setCount }
}
function App() {
const { count } = useCounter();
return <div>{count}</div>
}
Well, even though the custom hook is NOT a component, we are calling it from
within the component. That is why we are not violating the number one rule of
hooks. React knows, that the hook was called from within a component.
RULE #2: YOU MUST RENDER THE SAME NUMBER OF HOOK ON EACH COMPONENT RENDER. ERGO,
YOU CANNOT CONDITIONALLY CALL HOOKS.
For example, this example would fail:
function App() {
const [count, setCount] = useState(0)
if (count > 0) {
const [double, setDouble] = useState(count * 2)
return <div onClick={() => setDouble(double * 2)}>{double}</div>
}
return <div onClick={() => setCount(count + 1)}>{count}</div>
}
In the above example we tracked state in the count variable, until it reached 1.
Then we decided to start doubling the value. Unfortunately, this breaks the rule
of hooks as we can not render different number of hooks and you would see a
following error in your console:
[https://skillpies.s3.ap-southeast-2.amazonaws.com/courses/full-stack-development/sections/usestate-and-hooks-in-react/0_L8190K0pxLulYqL2.jpg]
To fix this you need to track both hooks on the component level:
function App() {
const [count, setCount] = useState(0)
const [double, setDouble] = useState(count * 2)
return (
count == 0
? <div onClick={() => setCount(count + 1)}>{count}</div>
: <div onClick={() => setDouble(double * 2)}>{double}</div>
)
}
🏭 BATCHING OF STATE UPDATES WITH USESTATE
In React, state updates are batched to optimize performance and minimize
unnecessary re-renders. Batching means that multiple state updates within the
same event are combined into a single re-render.
--------------------------------------------------------------------------------
HOW BATCHING WORKS
1. What is Batching?
* If you call setState multiple times in a single event handler, React
groups these updates and performs only one render.
2. When Does Batching Happen?
* React automatically batches state updates during synchronous events (e.g.,
onClick).
* In React 18 and later, asynchronous updates like setTimeout or API calls
are also batched.
--------------------------------------------------------------------------------
EXAMPLE: BATCHING IN ACTION
Here’s an example to demonstrate how React batches state updates:
import React, { useState } from "react";
const BatchingDemo = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClick = () => {
console.log("Before Updates:", count1, count2);
// Multiple state updates in one event
setCount1((prev) => prev + 1);
setCount2((prev) => prev + 1);
console.log("After Updates (Still stale):", count1, count2);
};
return (
<div>
<h1>React State Batching Example</h1>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<button onClick={handleClick}>Increment Both</button>
</div>
);
};
export default BatchingDemo;
--------------------------------------------------------------------------------
EXPLANATION:
1. Before State Update:
The console.log prints the stale state (values before the update).
2. State Updates:
* setCount1 and setCount2 are called sequentially.
* React batches both updates and processes them together in one re-render.
3. After Event:
The re-render occurs once, and both states are updated simultaneously.
--------------------------------------------------------------------------------
WHAT YOU SEE IN THE CONSOLE
When you click the button:
1. The Before Updates log will show the old values.
2. The After Updates log will still show stale values because state updates are
asynchronous.
3. After the render completes, both count1 and count2 are updated.
Maggie is a generative AI that can help you understand the course content better. You can ask her questions about the lecture, and she will try to answer them. You can also see the questions asked by other students and her responses.
Join the discussion to ask questions, share your thoughts, and discuss with other learners