devnotes.

useEffect: Side Effects & Lifecycle

useEffect lets a component perform side effects — anything that reaches outside of pure rendering, such as fetching data, subscribing to an event, manually touching the DOM, or setting a timer.

import { useState, useEffect } from 'react';

function StudentProfile({ id }) {
  const [student, setStudent] = useState(null);

  useEffect(() => {
    let ignore = false;
    fetch(`/api/students/${id}`)
      .then(res => res.json())
      .then(data => { if (!ignore) setStudent(data); });

    return () => { ignore = true; }; // cleanup
  }, [id]); // re-run whenever id changes

  if (!student) return <p>Loading...</p>;
  return <h2>{student.name}</h2>;
}

The dependency array controls when it runs

useEffect(() => { /* ... */ });          // after every render — rarely what you want
useEffect(() => { /* ... */ }, []);       // once, after the first render only
useEffect(() => { /* ... */ }, [id]);     // whenever `id` changes

React's ESLint plugin (eslint-plugin-react-hooks) will warn if you use a variable inside the effect but forget to list it as a dependency — trust that warning; missing dependencies are one of the most common sources of bugs.

Cleanup functions

Return a function from the effect to clean up before the next run or when the component unmounts — essential for subscriptions, timers, and event listeners:

useEffect(() => {
  const id = setInterval(() => console.log('tick'), 1000);
  return () => clearInterval(id); // runs on unmount or before the next effect
}, []);

useEffect(() => {
  function handleResize() { console.log(window.innerWidth); }
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

Common mistakes

  • Fetching without a cleanup/ignore flag can set state on an unmounted component or with stale data if id changes quickly.
  • Using useEffect to compute derived state — if a value can be calculated directly during render, just calculate it; don't put it in state + an effect.
  • Object/array/function dependencies that are recreated every render cause the effect to re-run every time — wrap them in useMemo/useCallback or move them inside the effect.
React useEffect Hook Explained with Examples | DevNotes