Hookscritical

useEffect

useEffect runs side effects after the component renders and DOM updates are committed. It takes a callback and an optional dependency array. The cleanup function runs before the next effect and on unmount.

Memory anchor

useEffect is like a post-it reminder that fires AFTER you've finished painting a room — you don't stop painting to read it, and you tear up the old reminder before reading the new one (cleanup).

Expected depth

Effects run after paint (not after render), so they don't block the browser from updating the screen. An empty dependency array [] means the effect runs once after mount. Omitting the array means it runs after every render. Each render captures its own props and state (closure), so the effect sees the values from the render that scheduled it. The cleanup function is essential for subscriptions, timers, and event listeners to prevent memory leaks.

Deep — senior internals

React compares dependencies with Object.is. If all dependencies are the same, the effect is skipped. In Strict Mode (development), React intentionally double-invokes effects to surface missing cleanups. useLayoutEffect is the synchronous alternative — it runs after DOM mutations but before paint, useful for measuring DOM elements or preventing visual flicker. In concurrent rendering, React may render a component but not commit it, meaning effects from that render never fire. Effects are technically scheduled as passive effects in a microtask-like queue after the browser paints.

🎤Interview-ready answer

useEffect is for side effects — data fetching, subscriptions, DOM manipulation. It runs after paint so it doesn't block rendering. I use the dependency array to control when it re-runs, and always return a cleanup function for subscriptions. The key mental model is that each render has its own effect with its own closure over that render's state and props.

Common trap

Putting an object or array literal in the dependency array causes the effect to re-run every render because a new reference is created each time. Either memoize the dependency or destructure to primitives.