Code:
useEffect(() => {
setTimeout(() => {
// this cause re-render twice
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}, 1000);
}, []);
My question is why this ponent 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.
codesandbox example
Code:
useEffect(() => {
setTimeout(() => {
// this cause re-render twice
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
}, 1000);
}, []);
My question is why this ponent 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.
codesandbox example
Share Improve this question edited Dec 23, 2020 at 15:35 Mario Petrovic 8,35215 gold badges43 silver badges66 bronze badges asked Dec 23, 2020 at 14:46 roy fangroy fang 413 bronze badges4 Answers
Reset to default 8EDIT (2023): React 18 has changed this behavior and now even this case will be batched.
Original answer (2020):
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 ponent 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.
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);
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./react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg./react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>
https://reactjs/blog/2022/03/08/react-18-upgrade-guide.html#automatic-batching
Now, it's something React handles itself.