Chapter 25 β€” useEffect and the Component Lifecycle

πŸ“– Definition

useEffect(callback, deps?) runs side effects (data fetching, subscriptions, DOM mutations, timers) after the component renders.

πŸ” Explanation

useEffect is React's escape hatch from pure rendering. The callback runs after the DOM has been updated.

Dependency Array

Form Runs when…
useEffect(fn) (no array) After every render
useEffect(fn, []) Once, after first mount
useEffect(fn, [a, b]) When a or b changes (by Object.is)
Returning a function inside Cleanup β€” runs before next effect or on unmount

Lifecycle Map (class β†’ hooks)

Class Functional equivalent
componentDidMount useEffect(fn, [])
componentDidUpdate useEffect(fn, [deps])
componentWillUnmount return () => {} inside effect

πŸ’» Code Example β€” Mount Only (componentDidMount)

useEffect(() => {
  console.log("Mounted once");
}, []);

πŸ’» Code Example β€” Cleanup (Unmount)

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

πŸ’» Code Example β€” Dependent Effect

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let cancelled = false;
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => { if (!cancelled) setUser(data); });

    return () => { cancelled = true; };   // ignore stale response
  }, [userId]);                            // re-run when userId changes

  return user ? <h2>{user.name}</h2> : <p>Loading…</p>;
}

πŸ’» Code Example β€” Subscription Pattern

useEffect(() => {
  const onResize = () => console.log(window.innerWidth);
  window.addEventListener("resize", onResize);
  return () => window.removeEventListener("resize", onResize);
}, []);

πŸ’» Code Example β€” Common Async Pitfall

// ❌ wrong β€” useEffect doesn't expect a Promise
useEffect(async () => {
  const data = await fetch("/api");
  setData(data);
}, []);

// βœ… right β€” define an async function inside
useEffect(() => {
  async function load() {
    const res = await fetch("/api");
    setData(await res.json());
  }
  load();
}, []);

πŸ’» Code Example β€” Stale Closure Bug

function Chat({ roomId }) {
  const [messages, setMessages] = useState([]);

  // ❌ Bug: handler captures the initial `messages = []`
  useEffect(() => {
    socket.on("message", (msg) => setMessages([...messages, msg])); // stale!
  }, []);

  // βœ… Fix: functional update
  useEffect(() => {
    socket.on("message", (msg) => setMessages(prev => [...prev, msg]));
  }, []);
}

πŸ’» Code Example β€” useLayoutEffect

import { useLayoutEffect, useRef } from "react";

function Tooltip({ targetRef, children }) {
  const tipRef = useRef();

  // Runs synchronously AFTER DOM mutation, BEFORE paint
  // β†’ no visual flicker
  useLayoutEffect(() => {
    const { top, left } = targetRef.current.getBoundingClientRect();
    tipRef.current.style.top = `${top - 30}px`;
    tipRef.current.style.left = `${left}px`;
  });

  return <div ref={tipRef} className="tooltip">{children}</div>;
}
useEffect useLayoutEffect
Runs After paint After DOM mutation, before paint
Blocks paint? No Yes
Use when Most cases You need to measure or mutate layout synchronously

πŸ’» Code Example β€” Skipping Effect Re-Runs

// ❌ inline object β†’ new reference every render β†’ effect re-runs forever
useEffect(() => {
  doSomething(options);
}, [{ x: 1 }]);

// βœ… memoize the object
const options = useMemo(() => ({ x: 1 }), []);
useEffect(() => {
  doSomething(options);
}, [options]);

// βœ… or extract primitives into deps
useEffect(() => {
  doSomething({ x: 1 });
}, []);

🌍 Real-World Impact

  • Data fetching, websocket subscriptions, third-party DOM integrations.
  • Cleanups prevent memory leaks and race conditions on prop changes.

🎯 Likely Interview Questions

  1. What does useEffect do?
  2. What is the dependency array?
  3. Difference between useEffect and useLayoutEffect?
  4. How do you replicate componentDidMount / componentDidUpdate / componentWillUnmount?
  5. Why shouldn't you make the effect itself async? β€” Effects must return either nothing or a cleanup function. An async function returns a Promise.

← Props vs State | Index | Next: useMemo / useCallback β†’