How does useEffect work? What goes in the dependency array?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
useEffect runs after render to synchronize with external systems. The dependency array lists everything the effect reads from render scope: empty [] = run once after mount, values = re-run whenever any changes, omitted = after every render. The returned cleanup runs before re-runs and on unmount.
Detailed explanation
Effects exist for things outside React's render flow — fetching, subscriptions, timers, direct DOM APIs. After each committed render whose dependencies changed, React runs cleanup from the previous effect, then the effect body. Dependency rules are strict: include every reactive value used inside (props, state, derived values); the ESLint exhaustive-deps rule enforces this because omitting deps creates stale-closure bugs where effects read outdated data. Empty array means the effect uses nothing reactive — mount-only semantics. For async work, define the async function inside and clean up with an ignore flag, since the effect itself can't be async: fetch → check if cancelled before setState to avoid 'update after unmount' races. Ask yourself 'what am I synchronizing with?' — if the answer is another component's data, you probably don't need an effect at all.
Official docs:React docs: useEffect
Example
useEffect(() => {
let ignore = false;
async function load() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!ignore) setUser(data);
}
load();
return () => { ignore = true; }; // cancel stale responses
}, [userId]);Interview tip
Explain the cleanup-then-run ordering explicitly, then show the ignore-flag pattern for fetches — it answers the most common follow-up ('how do you handle race conditions?').
Common mistakes
- Lying to the dependency array to avoid re-runs.
- Making the effect callback async directly (returns a promise, not cleanup).