Props and state
State is a snapshot
Understand why a state variable does not change inside the event handler that has already captured the current render.
Every render receives a snapshot of props and state. The event handlers created during that render keep seeing that snapshot, even after React schedules an update.
Consider three updates in one click:
function Counter() {
const [count, setCount] = useState(0)
function addThree() {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}
return <button onClick={addThree}>{count}</button>
}
If count is 0, every line requests setCount(0 + 1). React queues three updates to the value 1. The next render shows 1, not 3. Click the button and confirm that in the browser before reading on.
When the next value depends on a queued previous value, pass an updater function:
setCount(current => current + 1)
setCount(current => current + 1)
setCount(current => current + 1)
React processes the queue in order: 0 → 1 → 2 → 3. Each updater receives the latest queued value, not the stale count from the render that created the handler.
Batching is another reason multiple setState calls in one event can surprise you. React may group updates from the same event into one render. Updater functions still run in order inside that batch.
Snapshots also explain delayed handlers:
function showLater() {
setTimeout(() => alert(count), 2000)
}
The alert sees the count from the render that created showLater, even if another render happens before the timer runs.
This behavior prevents a running handler from changing underneath you. Use an updater when calculating the next state from previous state. Use a ref only when an asynchronous callback genuinely needs the latest mutable value without rendering it.
The same snapshot rule applies to props inside handlers. If you capture user.id in a closure and the parent passes a new user before the handler runs, the handler still sees the old id from its render.
Try both addThree versions and predict the result before clicking.
Lesson completed