useEffect
Everything so far has either rendered JSX or responded to a user-triggered event like a click. A side effect is anything that reaches outside the rendering process itself — fetching data, setting a timer, manually changing the document title, subscribing to something external. useEffect is the hook for running that kind of code at the right moment, tied to a component's render rather than to a click.
The Basic Shape
useEffect takes two arguments: a function containing the side effect itself, and a dependency array listing which values it depends on. React re-runs the effect function whenever any value in that array has changed since the last render — here, whenever title changes, the document's actual browser tab title updates to match.
The Three Forms of the Dependency Array
These three shapes cover almost every use case. No array at all is rare in practice — re-running on literally every render is usually a sign something belongs in the dependency array instead. An empty array is the standard choice for "run this once when the component first appears" (fetching initial data, setting up a subscription). A populated array is for "run this again whenever this specific thing changes."
Fetching Data on Mount
This is the classic shape of a data-fetching effect: state to hold the result (starting as null, meaning "not loaded yet"), an effect that fetches and calls the setter once the data arrives, and a conditional render that shows a loading state until user stops being null. [userId] ensures the fetch re-runs if a different user's profile needs to be shown — without it, switching to a new userId prop wouldn't trigger a new fetch at all. (Intermediate Chapter 6 covers more complete error/loading handling for this exact pattern.)
Cleaning Up After an Effect
Returning a function from inside the effect gives React a cleanup function, run right before the effect runs again, and also when the component is removed from the page entirely. setInterval here would otherwise keep ticking forever, even after Clock unmounts — clearInterval(id) in the cleanup function stops it at exactly the right moment. Anything that "starts" something ongoing (a timer, an event listener, a subscription) almost always needs a matching cleanup function to "stop" it.
exhaustive-deps); when in doubt, list every state/prop value the effect function actually reads.
| Dependency array | Effect runs... |
|---|---|
| (none) | After every render — rarely what's actually wanted |
| [] | Once, right after the first render |
| [value] | After the first render, and again whenever value changes |
Coding Challenges
Build a component that uses useEffect with an empty dependency array to set document.title to "Welcome!" once, when the component first appears.
📄 View solutionBuild a Stopwatch component with a seconds count state that increases by 1 every second using setInterval inside useEffect, with a correct cleanup function that clears the interval.
📄 View solutionBuild a component that takes a query prop and uses useEffect with [query] as the dependency array to log "Searching for: [query]" to the console every time query changes (simulate changing it with a couple of useState-driven buttons in the parent).
📄 View solutionChapter 8 Quick Reference
- useEffect(fn, deps) — runs
fnafter a render, based on the dependency array - No array — every render; [] — once on mount; [value] — on mount and whenever
valuechanges - Returning a function from the effect is a cleanup function — runs before the next effect, and on unmount
- Anything that "starts" something ongoing (timer, listener, subscription) needs a matching cleanup
- Missing dependencies → stale values; extra/wrong dependencies → possible infinite loops
- Next chapter: component composition and children — building flexible wrapper components