DevPath · Learn to code ESPTEN

useEffect, useRef and effects

useEffect: side effects

Beyond render

A component, in essence, computes JSX from props and state. But sometimes you need to do something that is not computing the interface: subscribing to a browser event, starting a timer, changing the tab title, talking to an API... This is called a side effect, because it affects something outside the component.

These actions should not happen during render (render must be pure and may run several times). That's what the useEffect hook is for: it says "run this code after painting, to synchronize with something external".

function Title() {
  const [n, setN] = useState(0);

  useEffect(() => {
    document.title = "Clicked " + n + " times";
  });

  return <button onClick={() => setN(n + 1)}>Click</button>;
}

useEffect receives a function (the effect) that React runs after rendering.

The dependency array

The second argument of useEffect is an array that controls when the effect runs again:

// 1) No array: on EVERY render
useEffect(() => { /* ... */ });

// 2) Empty array []: only ONCE, when the component MOUNTS
useEffect(() => { /* ... */ }, []);

// 3) With dependencies: on mount and every time one of them CHANGES
useEffect(() => { /* ... */ }, [userId]);

Golden rule: include in the dependencies every reactive value (props, state) you use inside the effect. If you omit it, you'll work with stale data.

The cleanup function

Many subscriptions must be undone: a timer must be stopped, a listener must be removed. For that, the effect can return a cleanup function. React runs it before re-running the effect and when the component unmounts.

function Clock() {
  const [sec, setSec] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setSec((s) => s + 1), 1000);
    return () => clearInterval(id); // cleanup: stops the interval
  }, []);

  return <p>{sec} s</p>;
}

Without that cleanup, the interval would stay alive after the component unmounts: a resource leak (and possible errors). Cleanup avoids duplicate subscriptions and leaves the system as it was.

While you render, think: "what needs to be connected?" (the effect) and "what needs to be disconnected?" (the cleanup).

Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
Fetching data with useEffect →