React useEffect without footguns
Share
TL;DR: Effects run after render. Keep each effect focused, derive dependencies correctly, and clean up subscriptions and timers. Use small recipes for the common cases.
When to use effects
- Synchronize with external systems like network, DOM, or storage.
- Set up subscriptions or timers and clean them up.
Do not use effects to compute values that can be derived during render.
Dependencies
List variables that the effect uses from the closure. If a value changes, the effect should re-run. Memoize functions or objects that would cause accidental reruns.
Recipes
Fetch data once when an id is ready
useEffect(() => {
if (!id) return;
let alive = true;
fetch(`/api/items/${id}`)
.then(r => r.json())
.then(data => { if (alive) setItem(data); });
return () => { alive = false; };
}, [id]);
Subscribe and clean up
useEffect(() => {
function onResize() { setWidth(window.innerWidth); }
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
Timer
useEffect(() => {
const t = setInterval(tick, 1000);
return () => clearInterval(t);
}, [tick]);
Common pitfalls
- Missing cleanup leads to leaks.
- Using effects for derived values that belong in render or useMemo.
- Forgetting dependencies causes stale reads.
Testing effects
Use fake timers and utilities that flush effects. Assert both the effect behavior and cleanup behavior.
FAQ
How do I avoid infinite loops
Do not create new objects or functions inside render unless memoized. Keep dependencies precise and use stable callbacks.
Where should I put async
Inside the effect. Do not pass async directly to useEffect, create and call an inner async function instead.
Cast this in your project
- React Interview Flashcards to drill hooks patterns.
- Vibe Coding T-shirt for your next pairing session.
Image credit: Photo by rovenimages.com: https://www.pexels.com/photo/yellow-bokeh-photo-949587/