Introduction
If you're a React developer, you've probably run into this classic example:
1const [count, setCount] = useState(0);
2function handleClick() {
3setCount(count + 1);
4setCount(count + 1);
5setCount(count + 1);
6console.log(count);
7// Logs 0, not 3!
8}So... is React delaying the state update? Is useState asynchronous? Or is something deeper happening? The real answer is understanding two of React's most important concepts: State as a Snapshot Batching React collects all state updates into a queue and processes them before the next render instead of rendering immediately after every setState.
What is a State Snapshot?
A snapshot is one of the reasons React is so fast. Every time React renders a component, it creates a snapshot of that render. That snapshot contains the current values of the component's state, props, context, and the event handlers created during that render. This means that every event handler "remembers" the state values that existed when that render happened. That's why inside the same event handler, count never changes, even if you call setCount() multiple times.
1const [count, setCount] = useState(0);
2function handleClick() {
3setCount(count + 1);
4 console.log(count);
5// Still 0
6 }React doesn't modify the current snapshot. Instead, it schedules another render with a new snapshot. This snapshot system is one of the biggest reasons React performs so well in large applications because React always works with a consistent view of your data during each render.
Why doesn't React update the UI immediately?
At first, it might sound logical for React to update the screen every time you call setState. But that would create another problem called Half-Finished Renders. Imagine you have:
1const [firstName, setFirstName] = useState("Ahmed");
2const [lastName, setLastName] = useState("Ali");The UI currently shows:
1//Ahmed AliNow you want to update both values:
1setFirstName("Youssef");
2setLastName("Mohammed");If React rendered after every single update, the UI could briefly become:
1//Youssef AliThe first name is updated, but the last name is still the old one. Then another render happens:
1//Youssef MohammedFor a brief moment, the UI displayed inconsistent data. That's exactly what React wants to avoid. Instead, React waits until the entire event handler finishes, batches all state updates together, and performs a single render. So the UI goes directly from:
1//Ahmed Alito:
1//Youssef Mohammedwithout ever showing an incomplete state. This is one of the biggest benefits of Batching.
When does React batch updates?
React batches state updates that happen during the same event, like: A button click An input change Any React event handler For example:
Each click is treated as a separate event. React never batches different user interactions together because every interaction should be processed independently.
Updating the same state multiple times
Sometimes you'll need to update the same state several times before the next render. For example:
1setNumber(number + 1);
2setNumber(number + 1);
3setNumber(number + 1);Most people expect the result to be 3. But it actually becomes 1. Why? Because every call reads the exact same number value from the current snapshot. If number is 0, React receives:
1setNumber(1);
2setNumber(1);
3setNumber(1);So the final value is simply:
1//1The solution: Updater Functions
React provides another way to update state:
1setNumber(n => n + 1);
2setNumber(n => n + 1);
3setNumber(n => n + 1);Instead of passing the next value directly, you pass a function that tells React how to calculate the next state from the previous one. React places these updater functions into its queue. After the event handler finishes, React executes them one by one:
1//0 → 1
2//1 → 2
3//2 → 3Now the final value becomes:
1//3The difference
When you pass a value:
1setCount(5);React simply replaces the state with that value. When you pass a function:
1setCount(prev => prev + 1);React uses the latest available state while processing the queue. That's why updater functions are the correct solution whenever the next state depends on the previous one.
Final Thoughts
React isn't "slow" or simply "asynchronous." It intentionally delays rendering to keep your UI consistent and avoid unnecessary work. Understanding State Snapshots and Batching explains why: console.log() still prints the old value. Multiple setState() calls don't immediately update the state. Functional updates (prev => ...) work correctly. React can render large applications efficiently while keeping the UI consistent. Once you understand these two concepts, many React behaviors that seem confusing suddenly make perfect sense.