State batching is how React groups multiple state updates together and performs a single re-render instead of one render per update. This improves performance and keeps the UI efficient.
In React 18, automatic batching was expanded — it now works not only in React event handlers (like onClick), but also in async functions, promises, timeouts, and more.
⚡ Key idea: Multiple state updates → one render
setState(prev => prev + 1) is safest when batching.React collects multiple state updates that happen in the same event loop tick and applies them together. Even if you call several setters one after another, React schedules a single re-render.
When batching, you should prefer functional updates so that each update works with the latest value:
// Use functional updates to safely batch multiple increments
const [count, setCount] = React.useState(0);
function handleClick() {
setCount((c) => c + 1);
setCount((c) => c + 1);
setCount((c) => c + 1);
// After batching, count increases by 3 in a single render
}
Without functional updates, each call might read a stale value of count, especially when multiple updates are batched together.
When you update multiple pieces of state in a single event handler, React batches them and re-renders once.
// React 18 automatically batches multiple state updates in one event
function Counter() {
const [count, setCount] = React.useState(0);
const [message, setMessage] = React.useState("");
const handleClick = () => {
setCount((c) => c + 1);
setMessage("Updated!");
// Both state updates above cause a single re-render
};
console.log("Rendered");
return (
<div>
<p>Count: {count}</p>
<p>{message}</p>
<button onClick={handleClick}>Click Me</button>
</div>
);
}
Even though setCount and setMessage are called separately, the component logs "Rendered" only once per click, because React batches the updates.
Before React 18, batching worked only in React event handlers. In async code (like fetch() or setTimeout()), each state update caused its own render. React 18 fixes this.
// React 18 batches state updates even after async work
function Example() {
const [count, setCount] = React.useState(0);
const [loading, setLoading] = React.useState(false);
const fetchData = async () => {
setLoading(true);
// Simulate network delay
await new Promise((r) => setTimeout(r, 1000));
setCount((c) => c + 1);
setLoading(false);
// All updates above are batched into a single re-render
};
console.log("Rendered");
return (
<div>
<p>Count: {count}</p>
<p>Loading: {loading ? "Yes" : "No"}</p>
<button onClick={fetchData}>Fetch Data</button>
</div>
);
}
Both setLoading and setCount are applied together: React does a single re-render after the async work completes, keeping the UI consistent and efficient.
In older versions of React, only updates inside React event handlers were batched. Async updates caused multiple renders.
// Before React 18, async updates like this caused multiple renders
async function updateValues() {
setCount(count + 1); // Triggers first re-render
await fetch("/api/data");
setLoading(false); // Triggers second re-render
}
With React 18’s automatic batching, both updates can be grouped, avoiding extra renders and improving performance.
In rare cases, you may need React to flush updates immediately, without batching. You can use flushSync from react-dom to do this.
// Use flushSync only when you really need an immediate render
import { flushSync } from "react-dom";
function Example() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
flushSync(() => setCount((c) => c + 1));
// Forces React to flush this update right away
console.log("Flushed render before continuing");
};
return <button onClick={handleClick}>Click</button>;
}
Use flushSync sparingly — mainly for critical UI updates (e.g., certain animations or syncing with external systems) where you cannot wait for the normal batched render.
count by 1.message to "Updated!"."Rendered" only once per click.Fetch Data sets loading to "Yes".count increments and loading becomes "No".flushSync is called, before any later code in the handler runs.prev => prev + 1) when batching multiple updates.setState.flushSync only for rare, critical UI updates where timing is crucial.count and message) in the same function and observe that React still does a single render.setTimeout() or an async fetch() call and confirm that React 18 automatically batches updates after the async work."Rendered" in your component body to visually confirm how often React re-renders.flushSync() to force an immediate render and compare it with the batched behavior.Goal: Understand how React 18 automatically batches multiple (even asynchronous) state updates to optimize rendering and improve performance in real-world components.