glo
glo

Reputation: 799

StrictMode + SetInterval = double renders (even with useEffect cleanup)

I'm trying to explain to another engineer how React StrictMode works - with specific reference to double renders, intervals and useEffect cleanups.

Take the following simple counter component:

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount((old) => old + 1);
    }, 3000);
    return () => clearInterval(id);
  }, []);

  console.log("RE_RENDERING");
  return <div>{count}</div>;
};

then mount it inside an App which uses StrictMode:

export default function App() {
  return (
    <StrictMode>
      <Counter />
    </StrictMode>
  );
}

We understand that strict mode causes React to trigger effects, and render twice, and that

React always cleans up the previous render’s Effect before the next render’s Effect.

(quoted verbatim from https://react.dev/learn/synchronizing-with-effects).

What I am struggling to explain is why exactly the console log "RE_RENDERING" still fires in batches of 2 every 3 seconds - even though we are cleaning up the setInterval as part of the useEffect(). E.g. the setInterval created in the first render should be cleaned up, leaving only the second interval.

Of course, when we remove <StrictMode> - "RE_RENDERING" reliably fires once via interval every 3 seconds. I can only assume that the state mutation, is somehow triggering the creation of a second setInterval which is not cleaned up - but I'm at a loss for how to explain why or how this is happening.

code sandbox link

Upvotes: 1

Views: 526

Answers (1)

Arkellys
Arkellys

Reputation: 7801

The part of the docs you quoted is actually misleading, a cleanup function will only be called when a component unmounts or when the dependencies of the hook change. If you add a console.log into your cleanup function, you will notice that it is only called once: when the component unmount because of the StrictMode:

This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.

However, if you add count into the dependencies array, the cleanup function will be called on every render. This is mentioned in the docs, on the same example you used (emphasis mine):

Now that you’re passing c => c + 1 instead of count + 1, your Effect no longer needs to depend on count. As a result of this fix, it won’t need to cleanup and setup the interval again every time the count changes.

As for your question, you said it yourself: the StrictMode will make your component re-render an extra time. You update the state every 3 seconds, so your component re-renders twice every 3 seconds. If you had multiple intervals running at the same time, here is what it would really do.

Upvotes: 1

Related Questions