useEffect Hook

useEffect Hook

useEffect lets you perform side effects in function components. Side effects include: data fetching, subscriptions, timers, manually updating the DOM, and logging.

Dependency Array โ€” The Key Concept

Dependency array When effect runs Equivalent (class)
None (omitted) After every render โ€” usually a bug! componentDidUpdate always
[] empty Once after first render only componentDidMount
[a, b] After first render, then when a or b changes componentDidUpdate checking prev

Data Fetching Pattern

function UserProfile({ userId }) {
  const [user, setUser]       = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError]     = useState(null);

  useEffect(() => {
    // Cancellation flag โ€” prevents state update after unmount
    let cancelled = false;

    async function fetchUser() {
      try {
        setLoading(true);
        setError(null);
        const res  = await fetch(`/api/users/${userId}`);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = await res.json();
        if (!cancelled) setUser(data);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    }

    fetchUser();

    // Cleanup: cancel state updates if userId changes or component unmounts
    return () => { cancelled = true; };
  }, [userId]); // Re-run whenever userId changes

  if (loading) return <Spinner />;
  if (error)   return <ErrorBanner message={error} />;
  return <UserCard user={user} />;
}

Subscriptions & Event Listeners

function WindowSize() {
  const [size, setSize] = useState({ w: window.innerWidth, h: window.innerHeight });

  useEffect(() => {
    const handler = () =>
      setSize({ w: window.innerWidth, h: window.innerHeight });

    window.addEventListener("resize", handler);

    // Cleanup: remove listener when component unmounts
    return () => window.removeEventListener("resize", handler);
  }, []); // [] = add once, remove once

  return <p>{size.w} ร— {size.h}</p>;
}

Timers

function Countdown({ seconds }) {
  const [remaining, setRemaining] = useState(seconds);

  useEffect(() => {
    if (remaining === 0) return;

    const timer = setInterval(() => {
      setRemaining(r => r - 1);
    }, 1000);

    return () => clearInterval(timer); // Cleanup!
  }, [remaining]);

  return <p>{remaining}s remaining</p>;
}
TipThe empty dependency array [] is your most common choice. Think of it as: “set things up once when the component appears, tear them down when it disappears.”

๐Ÿง  Test Yourself

What does returning a function from useEffect do?