setState in React 18
1. The main change - asynchrony and "smart" priority control
Before React 18, every setState() update was synchronous within a single render.
React would immediately trigger a re-render as soon as you called setState() (with possible batching inside an event).
Now, in React 18+ (Concurrent Mode):
setState()can run asynchronously and concurrently - React can defer, pause, merge, or cancel a render if other (higher-priority) updates are happening at the same time.
More simply:
React now decides:
- "Is this an urgent update? Then do it right away."
- "Is this not urgent (for example, filtering a list)? Then defer it while the user is typing."
2. Automatic Batching now works everywhere
Previously, batching (merging several setState() calls into one render) only worked inside React events (onClick, onChange, and so on).
Now it works in any context: setTimeout, Promise, async/await, fetch, and even inside custom hooks.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);
// React 18: one combined render
// React 17: two separate rendersIn other words: React now automatically groups all state updates within a single "iteration" of the event loop.
3. setState() can now be "deferred"
React 18 added the startTransition() API and the useTransition() hook,
which let you explicitly mark updates as "low priority".
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
startTransition(() => {
setFilter(value); // React will run this later, so it does not block input
});
}Here:
- the filter update (low priority) runs "in the background";
- the user's input (high priority) is handled instantly.
Previously, React could not split update priorities; now it can.
4. React can interrupt or cancel an in-progress render
If new state arrives while a render is in progress, React can:
- cancel the render that already started (if it has not finished yet);
- start a new one with more up-to-date data.
This became possible thanks to Concurrent Rendering. In old React 17, the rendering process was blocking - React had to "run to completion".
5. Ordering and predictability
The "update queue" mechanism (the setState queue) has become more flexible:
| Scenario | React 17 | React 18 |
|---|---|---|
Several setState calls in a row | Ran synchronously in a single render (inside an event) | Still merged, but order is guaranteed even with async calls |
| Updates in promises | Each setState triggered a separate render | All of them merge into a single combined render |
Updates inside startTransition | Did not exist | Updates run later, with low priority |
6. A comparison example
React 17 (outdated behavior)
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);
// Runs 2 renders (each setState in turn)React 18 (new behavior)
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);
// Runs 1 render (automatic batching)7. Forcing an update (flushSync)
If you need to update the DOM right away, bypassing batching,
you can use flushSync():
import { flushSync } from 'react-dom';
flushSync(() => {
setCount(c => c + 1);
});
// React re-renders the component immediatelyThis is useful when you need the update to appear before the next frame (for example, for precise layout measurements).
8. Behavior in Strict Mode (in dev mode)
In React 18, Strict Mode mounts and unmounts components twice,
to surface side effects related to asynchronous setState.
If you see setState being called twice, that is not a bug - it is a mechanism for checking the correctness of side effects.
Summary
In React 18,
setState()became:
- asynchronous and concurrent - React can queue updates and manage their priority;
- automatically batched in any context;
- interruptible - an in-progress render can be canceled if new state arrives;
- more performant and smoother for UX.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.