A Mental Model for React Hooks
Why hooks behave the way they do: renders as snapshots, effects as synchronisation, and how that explains stale closures and infinite loops.
SmartCampus Buddy TeamSeptember 12, 20268 min read
Hooks confuse people less once you accept two ideas: every render is a snapshot, and effects synchronise your component with something outside React.
Every render is a snapshot
When a component renders, React calls your function. The state and props you see inside that call are fixed values for that render. Calling a setter does not change the variable you already hold; it schedules a new render with a new value.
function click() {
setCount(count + 1);
setCount(count + 1); // still uses the same "count" from this render
}If the next value depends on the previous one, pass an updater function: setCount(c => c + 1).
Effects synchronise with the outside world
useEffect is for work that reaches outside React: subscriptions, timers, network requests, the document title. The dependency array tells React which values the effect reads. When any of them change, React runs the cleanup of the previous effect and then the effect again.
- No dependency array: runs after every render.
- An empty array: runs after the first render only, so anything it reads is frozen at its initial value.
- A list of values: re-runs when one of them changes.
Stale closures
Because each render is a snapshot, an effect with an empty dependency array that uses count will always see the first render's value. Either list the dependency, or use the updater form so the effect does not need to read the value at all.
Do not use an effect for everything
Values you can compute from props and state belong in the render body, not in state kept in sync by an effect. Event-driven work, such as submitting a form, belongs in the event handler.
Key takeaways
- Read state as a snapshot of one render.
- Use the updater form when the next state depends on the previous one.
- Keep dependency arrays honest, and write cleanup for anything you start.
- Derive values during render whenever you can.