React useEffect Hook

Synchronize components with external systems, manage dependencies, and prevent memory leaks with cleanup functions.

TL;DR

  1. Synchronize components with external browser or network APIs using useEffect.
  2. List all referenced reactive variables explicitly inside the dependency array.
  3. Prevent memory leaks by returning a dedicated cleanup cancellation function.

Dependency Array Behaviors

    Mount Only Effect

    Empty array runs effect once on initial mount.

    useEffect(() => {
      console.log('Component mounted!');
    }, []);
    Reactive Dependency

    Runs on mount and whenever listed dependencies change.

    useEffect(() => {
      document.title = `User: ${userId}`;
    }, [userId]);
    No Dependency Array

    Runs after every single render cycle without restriction.

    useEffect(() => {
      console.log('Rendered!');
    });

Cleanup Functions

    Window Event Listener

    Remove event listener during unmount to prevent leaks.

    useEffect(() => {
      const onResize = () => setWidth(window.innerWidth);
      window.addEventListener('resize', onResize);
      const clean = () => {
        window.removeEventListener('resize', onResize);
      };
      return clean;
    }, []);
    Interval Timer

    Clear active interval when component unmounts.

    useEffect(() => {
      const id = setInterval(tick, 1000);
      return () => clearInterval(id);
    }, []);
    WebSocket Connection

    Close active socket connection cleanly on teardown.

    useEffect(() => {
      const socket = createSocket(roomId);
      return () => socket.disconnect();
    }, [roomId]);

Data Fetching Patterns

    AbortController Fetch

    Cancel in-flight HTTP request when dependencies change.

    useEffect(() => {
      const c = new AbortController();
      fetch(`/api/user/${id}`, { signal: c.signal })
        .then((res) => res.json())
        .then(setData)
        .catch((err) => {
          if (err.name !== 'AbortError') setError(err);
        });
      return () => c.abort();
    }, [id]);
    Boolean Flag Cancellation

    Ignore stale async responses if user switched tabs.

    useEffect(() => {
      let active = true;
      fetchData().then((res) => {
        if (active) setData(res);
      });
      return () => { active = false; };
    }, [query]);
    Async IIFE Wrapper

    Invoke asynchronous function immediately inside effect.

    useEffect(() => {
      async function load() {
        const res = await fetch('/api');
        setData(await res.json());
      }
      load();
    }, []);

Common Pitfalls

    Infinite Render Loop

    Setting state inside effect without dependency guard.

    // BAD: Triggers infinite loop
    useEffect(() => {
      setCount((c) => c + 1);
    });
    Stale Closure Bug

    Missing state dependency reads frozen initial value.

    // BAD: count missing
    useEffect(() => {
      const log = () => console.log(count);
      const timer = setInterval(log, 1000);
      return () => clearInterval(timer);
    }, []);
    Unneeded Effect

    Compute derived state directly during render instead.

    // BETTER: No effect needed
    const fullName = `${first} ${last}`;

Tips

  1. Always list every reactive value referenced inside the effect within the dependencies array to satisfy the linter rules.
  2. Return a cleanup function to unsubscribe from sockets, remove eventListener triggers, or abort in-flight network requests.

Warnings

  1. Omitting dependencies causes stale closures where effects observe outdated props or state snapshots during execution.
  2. Do not use useEffect to transform data for rendering when standard inline derived variables calculate values instantly.

In Practice

FAQ