State, Batching and Immutability
State is a snapshot per render. Setters queue updates that React batches and applies on the next render, so think in queues, closures and immutable copies.
Key points
- 1
A setter never changes the variable in the current render; handlers, timeouts and async code all see the snapshot from the render that created them.
- 2
React processes queued updates in order: a value replaces the pending state, an updater
x => ...receives it. Use updaters when the next value depends on the previous one. - 3
Since React 18, updates are batched automatically everywhere (handlers, timeouts, promises, native listeners). An
awaitsplits updates into separate batches;flushSyncforces an immediate commit. - 4
React bails out when the new state is
Object.is-equal to the current one, so mutating an object or array and setting it again does nothing. Copy every level on the path to the change. - 5
useState(fn)is a lazy initializer;useState(fn())runsfnon every render. State keeps its first value, so copying props into state freezes them. - 6
Keep state minimal: no contradictory flags, nothing derivable during render, ids instead of copied objects, and normalized data for deep trees. Reset a subtree with a new
key. - 7
Reducers and updaters must be pure: StrictMode calls them twice in development.
dispatchis stable and reducers always receive the latest state, which avoids stale closures.
Common traps
setN(n + 1)three times adds 1, not 3, and a later value update overrides earlier updaters in the same queue.Mutating nested data inside a spread copy (
[...list]thenitem.done = true) changes objects other state still shares.Results from async handlers can arrive out of order; ignore or abort stale requests instead of trusting the last response.
Test yourself on State, Batching and Immutability
Ten questions, with the answer and explanation after each one.