Chapter 3 of 4

Cleanup

Returning a teardown function, and why React runs it more often than you expect.

An effect can return a function. React calls it before running the effect again, and once more when the component unmounts. Anything the effect started must be stopped there.

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

useEffect(() => {
  function handleResize() { setWidth(window.innerWidth); }
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Cleanup runs between updates, not only at the end

With dependencies, the sequence on each change is: run cleanup for the old values, then run the effect with the new ones. That is what keeps the subscription pointing at the right thing.

useEffect(() => {
  const connection = connect(roomId);
  return () => connection.close();
}, [roomId]);

// roomId: "general" -> "random"
// 1. close the "general" connection
// 2. open a "random" connection

Strict Mode runs effects twice

In development, React Strict Mode mounts each component, runs its effects, cleans them up, and runs them again. It is checking that your cleanup actually undoes the setup.

Cancelling stale requests

Async work needs guarding: a slow response for an old input must not overwrite a fresh one.

The flag stops a late response from a previous userId winning the race.
useEffect(() => {
  let isCurrent = true;

  async function load() {
    const data = await fetchUser(userId);
    if (isCurrent) setUser(data);
  }
  load();

  return () => { isCurrent = false; };
}, [userId]);