I am trying to understand the React hook set state cycle. With this example:
let counter = 0;
export const Example = () => {
const [state, setState] = React.useState({ isActive: false });
console.log(`render`);
counter++;
return (
<div>
<div
style={{ border: `1px solid ${state.isActive ? 'red' : 'black'}` }}
onMouseDown={() => setState(prevState => (prevState.isActive ? prevState : { isActive: true }))}
>
Text caption
</div>
Render - {counter}x
</div>
);
};
If I click on Text caption multiple times, the HTML will end up with Render - 2x. But there will be 3 console logs. When I click on it the first time, the state is changed. But for the second time I return the previous state. Why the code is executed after the second click?
>Solution :
The behavior you’re observing is a result of how React handles state updates and rendering. Let’s break down the process step by step:
Initial render: When the component is first rendered, the counter is set to 1 and the initial state is { isActive: false }.
First click: Upon clicking the "Text caption" div for the first time, the onMouseDown handler is triggered. This handler uses the functional version of setState, which takes the previous state as an argument. It evaluates whether the isActive property is already true in the previous state. If it’s true, it returns the previous state as is; otherwise, it creates a new state object with isActive set to true.
In this instance, the initial state is { isActive: false }, so the handler returns a new state object { isActive: true }, prompting a re-render.
Second click: After the first click, the state becomes { isActive: true }. When you click the "Text caption" div again, the onMouseDown handler is triggered once more. This time, the handler receives the previous state { isActive: true }. It checks if the isActive property is already true, which it is, leading to the handler returning the unchanged previous state.
Although the returned state is the same, React still schedules a re-render because a state update was initiated. This results in the second re-render taking place.
At this point, the counter is incremented to 2.
So, the sequence of events is as follows:
- Initial render (counter = 1).
- First click -> Re-render (counter = 2).
- Second click -> Re-render (counter = 3).
The reason you only see "Render – 2x" in the HTML is that React groups multiple state updates occurring during a single event loop iteration. As a result, the second re-render, triggered by the second click, doesn’t immediately update the counter displayed. Instead, React batches these updates and applies them collectively in a single render cycle. Consequently, you observe the final state as "Render – 2x".
For real-time updates of the counter displayed in the rendered output with every individual state update, consider utilizing the useEffect hook to update it after each rendering cycle.