'Why setState in setTimeout not batching?
Code:
useEffect(() => {
setTimeout(() => {
// this cause re-render twice
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}, 1000);
}, []);
My question is why this component re-render twice if we call two setCount in sequence.
Doesn't React batch multiple setCount in one go?
Thank you for answering or any suggestion.
Solution 1:[1]
Doesn't React batch multiple setCount in one go?
It does if it can. This is a case where it can not.
In order for react to batch things, the execution of code needs to start within react itself. For example, any component lifecycle events, or any synthetic event callbacks (eg, the onClick for a <button>). When that happens, react can let your code keep running, queuing up as many set states as you like, and then once your done you will return to react's code, which can then render the queued changes.
But if code execution did not start from react, then once you return, you're not going to be returning to react's code. So since they won't get to run code after yours, they do the render right away.
Solution 2:[2]
React will batch state updates if they're triggered from within a React-based event, like from inside a event handler or if they are called synchronously. It will not batch updates if they're triggered outside of React, like a setTimeout() or a Promise callback.
In your example the setCount is called from the context of a timer hence the batch updates did not happen. But if you call setCount multiple times from the context of a event handler or called synchronously, the state updates will be batched as shown in the snippet below.
Notice that the timer and the promise callbacks do not batch the updates:
function App() {
const [count, setCount] = React.useState(0);
console.log("re-render");
React.useEffect(() => {
setTimeout(() => {
// This cause re-render twice
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}, 2000);
// Will be batched as it it called
// In the context of React
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
//The state updates from the promise callback
//Will not be batched
Promise.resolve(1).then(data => {
console.log("Promise Resolved");
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
});
}, []);
const clickHandler = () => {
// Will be batched
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
return (
<div className="App">
<h1>{count}</h1>
<button onClick={clickHandler}>Click</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
Solution 3:[3]
The other answers explained why it is rendering several times.
Now, I want to give a solution to make it render only once. There is an API that allows you to make only one render: ReactDOM.unstable_batchedUpdates
In your example:
ReactDOM.unstable_batchedUpdates(() => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
});
}, 1000);
Solution 4:[4]
https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#automatic-batching
Now, it's something React handles itself.
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 | Nicholas Tower |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | Kid |
