QuestionsReact

useEffect dependencies & cleanup

EffectsMediumReact

Explain the dependency array and cleanup. What's the fetch-in-effect race condition and how do you fix it?

What it tests

The most misused hook. Dependency correctness and cleanup are senior signals.

Approach & answer

useEffect runs after commit; the dependency array decides WHEN it re-runs (empty = once, [x] = when x changes, omitted = every render). The cleanup function runs before the next effect and on unmount. The race: if a prop changes fast, an earlier fetch can resolve AFTER a later one and overwrite fresh data with stale. Fix with a cancelled/ignore flag in cleanup (or an AbortController) so a superseded response is dropped. Deeper points interviewers probe: the dependency array must list EVERY reactive value the effect reads (props, state, and functions/objects defined in render) — omitting one gives you a stale closure that silently reads old values; the honest fixes are to move the value inside the effect, wrap it in useCallback/useMemo, or use a ref. Many effects shouldn't exist at all: don't use an effect to transform data for rendering (compute during render), to reset state on prop change (use a key), or to handle a user event (do it in the handler). The mental model react.dev pushes is 'synchronize with an external system' — a subscription, the DOM, a network resource — and every synchronization needs its teardown, which is what cleanup is for.

Use this technique when

Data fetching tied to props, subscriptions, timers, event listeners — anything with setup that needs teardown.

Code

useEffect(() => {
  let ignore = false;
  fetch(`/api/user/${id}`)
    .then(r => r.json())
    .then(data => { if (!ignore) setUser(data); }); // drop stale response
  return () => { ignore = true; };  // cleanup on id change / unmount
}, [id]);

References