React State Batching
In this page:
Multiple Updates, One Re-render
When several state setters are called within the same event handler, React doesn't re-render after each one individually -- it waits until the handler finishes, then re-renders once with all the changes applied together. This keeps the UI fast and avoids showing partial, inconsistent states.
Note: You can safely call several different setters in a row inside one handler without worrying about extra re-renders.
Warning: This means you should never rely on the DOM being updated between two setState calls within the same handler -- it isn't yet.
Example: Multiple Updates, One Re-render
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function MultiUpdate() {
const [a, setA] = React.useState(0);
const [b, setB] = React.useState(0);
function handleClick() {
setA(a + 1);
setB(b + 1);
// Only ONE re-render happens, with both updates applied
}
return <button onClick={handleClick}>A: {a}, B: {b}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<MultiUpdate />);
</script>
</body>
</html>
Automatic Batching in React 18
Before React 18, batching mostly only happened inside handlers triggered directly by React (like onClick), but updates inside a setTimeout or a fetch's .then() would each cause a separate re-render. React 18 extended automatic batching to cover these cases too, further improving performance by default.
Note: If you're on React 18+, you generally don't need to think about where batching does or doesn't apply -- it's consistent everywhere.
Warning: Code written assuming React 17's more limited batching behavior may render differently (fewer renders) when upgraded to React 18.
Example: Automatic Batching in React 18
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function TimeoutUpdate() {
const [a, setA] = React.useState(0);
const [b, setB] = React.useState(0);
function handleClick() {
setTimeout(() => {
setA(prev => prev + 1);
setB(prev => prev + 1);
// React 18+: still batched into one re-render, even inside setTimeout
}, 200);
}
return <button onClick={handleClick}>A: {a}, B: {b}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<TimeoutUpdate />);
</script>
</body>
</html>
Why Batching Matters for Performance
Every re-render has a cost -- React has to run your component function again and compare the result to what was there before. Batching avoids doing this repeatedly for updates that logically belong together, keeping apps with lots of state changes noticeably faster.
Note: You don't need to manually optimize for batching -- it happens automatically; just avoid working around it with unnecessary workarounds.
Warning: Forcing extra re-renders (for example, by wrapping updates in unnecessary flushSync calls) undoes the performance benefit batching provides.
Example: Why Batching Matters for Performance
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function ManyUpdates() {
const [x, setX] = React.useState(0);
const [y, setY] = React.useState(0);
const [z, setZ] = React.useState(0);
function handleClick() {
setX(x + 1);
setY(y + 1);
setZ(z + 1);
// All three updates batched into a single render
}
return <button onClick={handleClick}>{x}, {y}, {z}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<ManyUpdates />);
</script>
</body>
</html>
- Assuming each setState call inside an event handler causes a separate, immediate re-render.
- Not realizing React 18 expanded automatic batching to cover more places (like inside promises and timeouts) than React 17 did.
- Reading a state variable immediately after calling its setter and expecting the batched update to already be reflected.
- Batching groups multiple state updates into a single re-render for efficiency.
- In React 18+, batching happens automatically almost everywhere, including timeouts and promises.
- Before React 18, automatic batching mostly only happened inside React event handlers.
- Batching improves performance by avoiding redundant, intermediate re-renders.
Automatic batching everywhere (not just React event handlers) was introduced in React 18; earlier versions only batched inside React's own event handlers by default.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: