A Setter Call Doesn't Change the Variable You're Holding
State Updates Are Asynchronous
Calling `setCount(n + 1)` doesn't change `count` immediately. The new value shows up on the next render.
What you'll learn
- Understand that state updates queue a re-render
- Avoid reading state right after setting it
- Recognize the "stale state" pitfall
A common surprise: setting state does not change the variable in your current render. It queues a re-render with the new value.
The Classic Confusion
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // logs the OLD count, not the new one
}
return <button onClick={handleClick}>{count}</button>;
} count is a const captured in this render. setCount(...) tells
React “next time this component renders, use the new value”. The
new value shows up in the next run of the function.
Snapshots, Not Live References
Every render gets its own snapshot of state. Inside one render,
count is a fixed value. Three setter calls don’t stack:
function handleClick() {
setCount(count + 1); // count is 0 → schedule 1
setCount(count + 1); // count is still 0 → schedule 1
setCount(count + 1); // count is still 0 → schedule 1
}
// Final count after click: 1, not 3 All three calls read the same count (the snapshot for this
render). To stack, use the updater function form (next lesson).
Reading the Latest Value
You usually don’t need to. If you want to do something after a
state change, do it on the next render with useEffect —
covered in chapter 4.
Setting Objects and Arrays
Don’t mutate. Always replace:
const [user, setUser] = useState({ name: "", age: 0 });
// ✗ Mutating — React doesn't notice, no re-render
user.name = "Ada";
setUser(user);
// ✓ Replace with a new object
setUser({ ...user, name: "Ada" }); Same for arrays — setItems([...items, newItem]), not
items.push(newItem).
The “Set, Then Read” Anti-Pattern
// ✗ Doesn't work the way you'd hope
function handleAdd() {
setItems([...items, newItem]);
console.log(items.length); // OLD length
send(items); // OLD items
} Either compute the value once and use it twice:
function handleAdd() {
const nextItems = [...items, newItem];
setItems(nextItems);
send(nextItems);
} Or move the dependent work to an effect.
Up Next
When the new state depends on the previous state, use the updater function form.
Updater Functions →