'React hooks: Why do several useState setters in an async function cause several rerenders?
This following onClick callback function will cause 1 re-render:
const handleClickSync = () => {
// Order of setters doesn't matter - React lumps all state changes together
// The result is one single re-rendering
setValue("two");
setIsCondition(true);
setNumber(2);
};
React lumps all three state changes together and causes 1 rerender.
The following onClick callback function, however, will cause 3 re-renderings:
const handleClickAsync = () => {
setTimeout(() => {
// Inside of an async function (here: setTimeout) the order of setter functions matters.
setValue("two");
setIsCondition(true);
setNumber(2);
});
};
It's one re-render for every useState
setter. Furthermore the order of the setters influences the values in each of these renderings.
Question: Why does the fact that I make the function async (here via setTimeout
) cause the state changes to happen one after the other and thereby causing 3 re-renders. Why does React lump these state changes together if the function is synchronous to only cause one rerender?
You can play around with this CodeSandBox to experience the behavior.
Solution 1:[1]
If code execution starts inside of react (eg, an onClick
listener or a useEffect
), then react can be sure that after you've done all your state-setting, execution will return to react and it can continue from there. So for these cases, it can let code execution continue, wait for the return, and then synchronously do a single render.
But if code execution starts randomly (eg, in a setTimeout
, or by resolving a promise), then code isn't going to return to react when you're done. So from react's perspective, it was quietly sleeping and then you call setState
, forcing react to be like "ahhh! they're setting state! I'd better render". There are async ways that react could wait to see if you're doing anything more (eg, a timeout 0 or a microtask), but there isn't a synchronous way for react to know when you're done.
In the current version of react, you can tell react to batch multiple changes by using unstable_batchedUpdates
:
import { unstable_batchedUpdates } from "react-dom";
const handleClickAsync = () => {
setTimeout(() => {
unstable_batchedUpdates(() => {
setValue("two");
setIsCondition(true);
setNumber(2);
});
});
};
Once react 18 arrives, this won't be necessary, since the changes they've made to rendering for concurrent mode will get rid of the need for this.
Solution 2:[2]
Right now react only batches sync setState
s inside event handlers.
But in react 18 it will be available in setTimeout
, useEffect
s etc
Here is excellent explanation from Dan https://github.com/reactwg/react-18/discussions/21
Solution 3:[3]
UPDATE: REACT 18 FEATURES AUTOMATIC BATCHING
In React, Batching helps to reduce the number of re-renders that happen when state changes, when you call setState()
. Previously, React batched state updates in event handlers, for example :
const handleClick = () => {
setCounter();
setActive();
setValue();
}
//re-rendered once at the end.
However, state updates that happened outside of event handlers were not batched. For example, if you had a promise or were making a network call, the state updates would not be batched. Like this:
fetch('/network').then( () => {
setCounter(); //re-rendered 1 times
setActive(); //re-rendered 2 times
setValue(); //re-rendered 3 times
});
//Total 3 re-renders
As you can tell, this is not performant. React 18 introduces automatic batching which allows all state updates – even within promises, setTimeouts, and event callbacks – to be batched. This significantly reduces the work that React has to do in the background. React will wait for a micro-task to finish before re-rendering.
Automatic batching is available out of the box in React, but if you want to opt-out you can use flushSync
.
More Reading from FREECODECAMP RESOURCES: https://www.freecodecamp.org/news/react-18-new-features/
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | zhulien |
Solution 2 | zhulien |
Solution 3 | Imran Rafiq Rather |