React state does not update immediately because setting state only changes it for the next render. The variable you already have keeps its old value until that render happens. React also waits until your event handler finishes before it applies the update, which groups several changes into one pass.
The symptom
A common surprise looks like this: the screen updates, but a log right after the setter shows the old value. The same old value appears even inside a timer that runs much later.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count);
}
return <button onClick={handleClick}>Clicked {count} times</button>;
}The button text moves from 0 to 1, but the log still prints 0. Calling the setter did not change the count variable that this render already created.
State is a snapshot
Each render receives a fixed value for every piece of state. The setter does not mutate that value.
It records the next value and asks React to render again, and only the next render sees the new value. The variable you hold right now is sealed for this render.
Think of state as a photograph of the UI at one moment. The handler can only read the photograph it was given, even if it runs after several seconds. That is why the old value keeps appearing.
A timer makes this visible. If the handler sets a value and then reads it inside a setTimeout, the timer still sees the old snapshot.
The state inside React may have moved on, but the closure kept the value from the render where it was created. The stale value is correct for that closure, even when it looks wrong next to the updated screen.
Batching waits for the handler
React also delays the re-render until all the code in your handler has run. It collects every update, then applies them together. This is called batching, and it is why no part of the screen changes mid-handler.
Think of a waiter writing down a full order before walking to the kitchen. React records every update first, then serves one re-render with the final values, so you never see a partially updated screen. Batching also means one event costs one render, no matter how many set calls it makes.
How to read the latest value
There are three ways to get the value you need without fighting the snapshot.
- To calculate a next value from the previous one, pass an updater function.
- To show a value that changes, render it directly instead of reading it in the same handler.
- To run code after a render, use an Effect only for real synchronization with an external system.
The setter's job is to schedule the next render. The useState guide covers the full behavior if you want the fundamentals again.
For a form, the value on screen always matches the state of the current render, so you rarely need to read a just-set value. The staleness only surprises people who log or inspect state inside the same handler that changed it.
Common mistake
Logging state immediately after setting it is the most common confusion. If you need the value you just computed, store it in a local variable and use that, rather than reading state before the next render.
A second confusion is mutating state and expecting a re-render. An object or array that changes in place still looks identical to React, so it skips the update entirely. Replace the value instead.
What to learn next
Once the snapshot idea clicks, updater functions and batching cover the rest of state timing. From there, immutable updates to objects and arrays keep the snapshot model working in practice. Understanding why the value is stale turns a confusing bug into predictable behavior.
Rune AI
Key Insights
- Setting state only changes it for the next render.
- Each render holds a fixed snapshot of every state value.
- React batches updates until the event handler finishes.
- Reading state right after the setter returns the old value.
- Use a local variable or an updater function to read the latest value.
Frequently Asked Questions
Why does console.log show the old state after setState?
Does batching make state updates slower?
How can I read the value I just computed?
Conclusion
Setting state schedules the next render instead of changing the current variable. The value looks stale because each render holds a fixed snapshot, and batching delays the update until the handler ends.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.