State & Props
What re-renders, when, and why your value is stale.
45 minDifficulty 2/5reactAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
You update a variable inside a component and the screen doesn't change at all. React has no idea a plain variable assignment happened — it only re-renders in response to `useState`'s setter function being called, because that's the specific signal it watches for. A regular variable is invisible to it.
The mental model
State is data a component remembers between renders, and calling its setter is how you tell React "something changed, please re-render." Each render of a component is a fresh function call with its own snapshot of state, props, and any values computed from them — a value from render one doesn't magically update in a closure that already captured it.
How it works
Calling the setter schedules a re-render; it doesn't update state synchronously
`setCount(count + 1)` doesn't make `count` equal to the new value immediately in the same line of code that called it — `count` inside that render's closure stays whatever it was for the rest of THIS render. The new value appears the NEXT time the component function runs, which React schedules to happen soon but not instantly, mid-function.
Stale closures are the single most common state bug
A function defined inside a render — an event handler, an effect callback — closes over that specific render's value of state. If that function runs later, after several more renders have happened with newer state, it still sees the OLD value it originally captured, unless it reads state via a functional updater (`setCount(c => c + 1)`) that always receives the latest value.
Multiple setState calls in one event handler batch into one re-render
Calling `setA(1)` and `setB(2)` back to back inside the same event handler doesn't trigger two separate re-renders — React batches them and re-renders once with both updates applied. This is a performance optimisation, and it's why reading state right after calling its setter, in the same function, still shows the old value: no re-render has happened yet to produce the new one.
State must be replaced, not mutated
Pushing directly into a state array (`items.push(newItem)`) mutates the existing array object without creating a new one, and React's default comparison — checking if the reference changed — sees the same reference and may skip re-rendering entirely. The correct pattern creates a new array or object: `setItems([...items, newItem])`.
The mechanism
Calling a state setter schedules a re-render rather than mutating anything in place. React re-invokes the component function, which runs top to bottom fresh, calling `useState` again — this time returning the NEW value React has stored for that state slot — and produces a new JSX tree. The old render's variables, including any captured in already-created closures, are untouched and now simply describe the past.
What people get wrong
- Reading a state variable right after calling its setter, in the same function, will show the updated value.
- The setter schedules a re-render for later; the current render's variable stays at its original value for the rest of that function call, no matter how many lines come after the setter call. This trips up nearly everyone at first, because in plain JavaScript, assignment is synchronous — React deliberately breaks that intuition to enable batching and consistent snapshots per render.
- You can safely mutate a state object or array directly as long as you also call the setter afterward.
- Mutating in place changes the SAME reference, and calling the setter with that same reference can make React's equality check conclude nothing changed, skipping the re-render entirely. This produces a genuinely confusing bug: the state IS technically updated in memory, but the screen never reflects it, because React never knew to re-render.
- State updates always happen immediately, synchronously, one render per setter call.
- React batches multiple setter calls within the same event handler (and in newer versions, most async callbacks too) into a single re-render for performance, rather than re-rendering after each individual call. Assuming one render per setState leads to incorrect mental models about performance and about when exactly a re-render will actually happen.
When not to use it
- The value doesn't affect what's rendered and doesn't need to trigger a re-render — like a mutable counter used only inside an event handler's internal logic.
- A `useRef`, which persists a value across renders without causing a re-render when it changes — appropriate for values React's rendering doesn't need to know about.
- State needs to be shared and updated by several distant components without prop-drilling a setter through many layers.
- Context (see `react-context`) or a dedicated state-management library, depending on how wide and how frequently the sharing happens.
Terms
- useState
- — The hook that gives a component a piece of state and a setter function to update it, triggering a re-render when called.
- Stale closure
- — A function that captured an old value of state or props from a previous render and continues to see that old value, rather than the current one.
- Batching
- — React combining multiple state updates that happen close together into a single re-render, rather than one render per update.
- Functional updater
- — Calling a setter with a function of the previous value, like setCount(c => c + 1), guaranteeing it operates on the latest state rather than a possibly-stale captured value.
In an interview
Why does calling a setState setter twice in a row with the same computed value sometimes only apply the update once?
- if both calls read the same stale captured value (e.g. setCount(count + 1) twice), both compute the same new value from the same old count
- using a functional updater, setCount(c => c + 1) twice, correctly applies both increments because each receives the latest pending value
Can you recall it?
Why does reading a state variable immediately after calling its setter, within the same function, still show the old value?
Connected ideas
- The Rendering Model — Render, reconcile, commit — the mental model that makes performance work predictable.
- Context & State Sharing — Avoiding prop drilling without accidentally re-rendering your whole tree.
- Forms & Validation — Controlled vs uncontrolled, and validating the same rules on both sides of the wire.