State Snapshot & Purity
State is a snapshot, not a live variable
Section titled “State is a snapshot, not a live variable”This is the single most misunderstood thing in React. When you read a state variable, you are reading its value for this render — a snapshot frozen at the moment the component was called. It does not change mid-render, even after you call the setter.
function Counter() { const [count, setCount] = useState(0);
function handleClick() { setCount(count + 1); setCount(count + 1); setCount(count + 1); // count is 0 for this whole render. All three read the SAME snapshot (0), // so this sets state to 0 + 1 = 1, three times. Result: 1, not 3. }
return <button onClick={handleClick}>{count}</button>;}To update based on the latest pending value, pass an updater function — React runs them in order against the evolving value:
setCount((c) => c + 1);setCount((c) => c + 1);setCount((c) => c + 1);// Each receives the previous pending value: 0→1→2→3. Result: 3.The mental model: a re-render is a fresh call of your function with a new snapshot. The old render’s variables are gone; the new render sees the new values.
Treat state as immutable
Section titled “Treat state as immutable”Because state is a snapshot React compares by identity, you must never mutate it. Create new objects/arrays instead:
// ❌ Mutation — same array reference, React may not re-render, and it's a bug.todos.push(newTodo);setTodos(todos);
// ✅ New reference — React sees a change and re-renders.setTodos([...todos, newTodo]);Mutating state breaks React’s ability to detect changes (it compares references) and corrupts the snapshot model. Always produce a new value.
Render must be pure
Section titled “Render must be pure”A component’s render — everything it does while computing its returned JSX — must be pure: given the same props and state, it returns the same output and causes no side effects. During render you must not:
- mutate props, state, or any pre-existing variable/object;
- write to the DOM, make network requests, or start timers;
- read or write anything outside the function that could change.
flowchart LR inputs["props + state (snapshot)"] --> render["render (pure): compute JSX only"] render --> out["same inputs → same JSX"] render -. NOT here .-> se["side effects: DOM, network, timers"] se --> eff["→ event handlers & effects"]
Side effects belong in event handlers (things that happen on interaction) and effects (useEffect, for synchronizing with external systems) — never in the render body. This purity is what lets React call your component whenever it likes.
StrictMode double-renders on purpose
Section titled “StrictMode double-renders on purpose”In development, React’s <StrictMode> deliberately calls your component function twice (and runs effects setup→cleanup→setup). This is not a bug — it’s a detector. If your render is pure, calling it twice produces identical output and nothing breaks. If you accidentally mutate something or rely on a side effect during render, the double-invocation surfaces the bug immediately, in development, instead of as a mysterious glitch in production.