Hooks
useEffect, races & when not to use it
Dependencies, cleanup, race conditions, and effect-vs-render distinctions.
useEffect synchronises a component with external systems — network, subscriptions, timers, non-React widgets — running after paint. The dependency array declares what it reads; a correct array is essential.
An effect is a subscription to the outside world with an unsubscribe note attached; forget the note and old broadcasts keep overwriting your screen.
Key concepts
1
Race conditions: if an effect fetches on id and id changes fast, an older response can overwrite a newer one. Guard with an ignore flag or an AbortController in cleanup.
Race conditions:idignoreAbortController
2
Modern React guidance: you might not need an effect. Derived data belongs in render/useMemo; responding to events belongs in handlers. Effects are for genuine external synchronisation, not for chaining state.
you might not need an effectuseMemo
3
Pitfall: empty deps [] with a stale closure reading old state. useLayoutEffect runs synchronously before paint (measure/mutate DOM) — use sparingly. Interview angle: "how do you prevent a stale fetch from clobbering fresh state?" — cleanup flag / abort.
Pitfall:Interview angle:[]useLayoutEffect
jsx
useEffect(() => {
const ac = new AbortController();
fetch(`/api/users/${id}`, { signal: ac.signal })
.then(r => r.json())
.then(setUser)
.catch(e => { if (e.name !== 'AbortError') throw e; });
return () => ac.abort(); // cancels stale request
}, [id]);