PresentationInteractive, narrated presentation of the section content.
ContentDetailed description of the section content.
React = Reactivity

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:
useStatehook to control the state and reactivity of the componentuseEffectto control the side effects and clean up after the componentuseMemoto 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:
pop
shift
unshift
splice
reverse
Instead, we need to use methods that modify the array and return a nbew object reference such as:
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:

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
- What is Batching?
- If you call
setStatemultiple times in a single event handler, React groups these updates and performs only one render.
- If you call
- When Does Batching Happen?
- React automatically batches state updates during synchronous events (e.g.,
onClick). - In React 18 and later, asynchronous updates like
setTimeoutor API calls are also batched.
- React automatically batches state updates during synchronous events (e.g.,
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:
- Before State Update:
Theconsole.logprints the stale state (values before the update). - State Updates:
setCount1andsetCount2are called sequentially.- React batches both updates and processes them together in one re-render.
- After Event:
The re-render occurs once, and both states are updated simultaneously.
What You See in the Console
When you click the button:
- The
Before Updateslog will show the old values. - The
After Updateslog will still show stale values because state updates are asynchronous. - After the render completes, both
count1andcount2are updated.
No hints available.