How (and why) to use useEffect's cleanup - Intervals

Using setInterval with useEffect can be confusing. Often they will overlap, or use stale data. We can prevent this by properly clearing the intervals inside useEffect's cleanup function.

How (and why) to use useEffect's cleanup - Intervals

Using setInterval with useEffect can be confusing. Often they will overlap, or use stale data. We can prevent this by properly clearing the intervals inside useEffect's cleanup function.

Si querés leer este post en español, clickeá acá.

What happens if we don’t clear the intervals?

Suppose we have a component which shows blinking text, like we used to have in html.

const Blinker = ({ text }) => {
  const [visible, setVisible] = useState(true);
  useEffect(() => {
    setInterval(() => {
      console.log(`Current blinking text: ${text}`);
      setVisible((visible) => !visible);
    }, 1000);
  }, [text]);
  return visible ? <h1>{text}</h1> : null;
};
Note: The [text] dependency of useEffect is causing the hook to re-execute every time the prop text changes. The only reason to add this dependency is to make the console.log work properly.

Since we could consider it to be non-vital to our app, we could remove it. But considering there are lots of valid reasons to add a prop to the dependencies, let’s assume the console.log is a strong requirement.

What happens with Blinker when we render for the first time (i.e on mounting)

  1. The effect is run for the first time, since effects always run on the first render (mounting).
  2. The first interval is started, which logs a Current blinking text: string every second.
  3. The component returns an empty header, which the browser renders.

What happens with Blinker when we change the text prop to “a”

  1. The Blinker component renders again, since whenever props or state change, react will re-render our component.
  2. React checks the useEffect’s dependencies, and since one changed (text), it executes the effect’s function again.
  3. A new interval is registered, which will print Current blinking text: a every second.
  4. The component returns a header with the letter “a”, which also shows up on the screen.

In this scenario, the browser’s console looks like this:

browser's console screenshot

Two intervals are running at the same time, each logging a different thing. This happens because we didn’t delete the old interval before creating a new one, so the old one never stopped logging!

Solution

To solve this, we can use useEffect’s cleanup function, which looks like this:

const Blinker = ({ text }) => {
  const [visible, setVisible] = useState(true);
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log(`Current blinking text: ${text}`);
      setVisible((visible) => !visible);
    }, 1000);
    return () => {
      clearInterval(intervalId);
    };
  }, [text]);
  return visible ? <h1>{text}</h1> : null;
};

What happens now is:

  1. The Blinker component renders again, same as before, since props changed.
  2. React checks useEffect’s dependencies, and since they changed, it executes the effect’s function again.
  3. But first, before react executes the effect, it will run the function we returned, cleaning up the previous effect’s function and deleting the old interval.
  4. A new interval is registered, which will print The text currently blinking is: a every second.
  5. The component returns a header with the letter “a”, which also shows up on the screen.

This prevents overlapping of the intervals, and makes our code behave the way we wanted. Keep in mind React will run this cleanup function before re-running an effect, and when unmounting the component. So basically we cleanup before reacting to changes, and when we don’t need the component anymore.

You can see both examples above working in this codesandbox, remember to switch the exported component in Blinker.js to see both behaviors shown in this article.

There are many other scenarios where useEffect’s cleanup function is useful or necessary, stay tuned for more examples in the future!
_________________________________________________________

Did you enjoy this post? We recommend these ones as well:

Virtual DOM: ReactJS implementation
How does the Virtual DOM work? And what is actually the DOM?If you ever have wondered about those questions, this post is for you.
Frontend frameworks: Criteria & tips to become proficient with new tools
The goal of this post isn’t to analyze which framework is better, but to providea set of tools and a couple of tips to ease the learning of a new framework In the early days of software development applications were made, from start tofinish, monolithically. Since computers were expensive and not…
“Is it CSS? Yes? Next.” - Tips for CSS code reviews
CSS can make stuff look super cool... but is the code that makes cool stuff cool too? Not sure what to check? Some tips to do so might help!