React useEffect without footguns

React useEffect without footguns

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

Image credit: Photo by rovenimages.com: https://www.pexels.com/photo/yellow-bokeh-photo-949587/

Back to blog

Leave a comment

Please note, comments need to be approved before they are published.