useEffect
Run side-effects after render, fetch data, set up subscriptions, manipulate the DOM. The dependency array controls exactly when your effect fires.
01 — What it is
useEffect(() => {
// side effect here
return () => {
// cleanup (optional) — runs before next effect or unmount
};
}, [dependencies]);[] = run once on mount. No array = run after every render. Listed deps = run when those values change.// 1. Run once on mount
useEffect(() => { fetchData(); }, []);
// 2. Run when 'id' changes
useEffect(() => { fetchUser(id); }, [id]);
// 3. Run after every render (rarely needed)
useEffect(() => { document.title = count; });02 — Common patterns
Fetch on mount with an empty dependency array — the most common pattern.
useEffect(() => {
fetch("https://api.example.com/data")
.then((res) => res.json())
.then((data) => setData(data));
}, []); // ← empty array = run onceReturn a cleanup function to cancel subscriptions, clear timers, or abort fetches when the component unmounts or before the effect re-runs.
useEffect(() => {
const controller = new AbortController();
fetch("/api/data", { signal: controller.signal })
.then((res) => res.json())
.then(setData);
return () => controller.abort(); // cleanup
}, []);If you use a value inside an effect but don't list it as a dependency, the effect captures its initial value and never updates. Always include everything you read.
// ✗ stale — 'count' is always 0 inside the effect
useEffect(() => {
console.log(count);
}, []);
// ✓ correct — runs whenever count changes
useEffect(() => {
console.log(count);
}, [count]);03 — Live demo
The fetch runs once on mount (empty deps). The counter increments independently — notice the fetch doesn't re-run when you click.
Counter re-renders the component — the fetch does not re-run (empty deps [])