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.
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).
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.
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.
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.
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.