Learn / Frameworks / React / Effects with useEffect

Intermediate 16 min

Effects with useEffect

Synchronize with the outside world: fetching, timers and subscriptions.

What you will learn

  • Write effects with correct dependencies
  • Clean up effects
  • Know when not to use an effect

Rendering should be a pure calculation: same props and state in, same JSX out. But apps also need to talk to things outside React: servers, timers, the browser title, event listeners. Effects are the escape hatch. useEffect runs your code after React has updated the screen.

import { useState, useEffect } from "react";

function Title({ name }) {
  useEffect(() => {
    document.title = `Hello ${name}`;   // runs after render
  }, [name]);                            // ...only when name changes
  return <h1>{name}</h1>;
}

The second argument is the dependency array:

  • [a, b]: run after the first render and whenever a or b changed.
  • []: run once after the first render.
  • omitted: run after every render (rarely what you want).

List every reactive value the effect reads (props, state, values derived from them). The lint rule react-hooks/exhaustive-deps checks this for you; treat its warnings seriously, because stale values are a top source of bugs.

Cleanup

An effect may return a function that React calls before the effect re-runs and when the component unmounts. Use it to stop timers, remove listeners and cancel subscriptions.

function Clock() {
  const [now, setNow] = useState(new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);     // cleanup
  }, []);
  return <p>{now.toLocaleTimeString()}</p>;
}

In development, StrictMode runs effects twice (mount, unmount, mount) on purpose. If your effect breaks when run twice, it is missing a cleanup.

function User({ id }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    let ignore = false;                 // avoid setting state from a stale request
    fetch(`/api/users/${id}`)
      .then((r) => r.json())
      .then((data) => { if (!ignore) setUser(data); });
    return () => { ignore = true; };
  }, [id]);

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

You might not need an effect

  • Computing a value from props or state: do it during render, no effect needed.
  • Responding to a click or submit: put the logic in the event handler.
  • Resetting state when a prop changes: give the component a different key.
Infinite loop

An effect that sets state, with that same state in its dependency array, can loop forever. Also, an object or function created inline changes identity on every render, so listing it as a dependency re-runs the effect each time.

Try it yourself

Write a useWindowWidth-style effect: track window.innerWidth in state, update it on the resize event and remove the listener on cleanup.

Show solution
function Width() {
  const [w, setW] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setW(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return <p>{w}px</p>;
}