The cleanup callback of useEffect

February 4, 2021

Cleaning up window event listeners

In this example, we have a component named WindowSize that updates its state with the window's width and height whenever a window resize event occurs. To achieve this, we use the useEffect hook with an empty dependency array, ensuring the effect only runs on mount and unmount. The effect's body executes on mount, and the returned function executes on unmount.

Whenever we use window.addEventListener() inside a useEffect hook, we must remove the event listener when the component unmounts to avoid adding global event listeners repeatedly and potentially causing memory leaks.

import React, { useState, useEffect } from 'react';

export default function WindowSize() {
  const [size, setSize] = useState({
    width: 0,
    height: 0,
  });

  useEffect(() => {
    // Define the listener callback function
    const onWindowResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    // Initialize state with current window size
    onWindowResize();

    // Add window resize event listener on mount
    window.addEventListener('resize', onWindowResize);

    return () => {
      // Remove window resize event listener on unmount
      window.removeEventListener('resize', onWindowResize);
    };
  }, []);

  return (
    <p>
      Window width: {size.width}
      <br />
      Window height: {size.height}
    </p>
  );
}

Cleaning up setInterval and setTimeout

Similarly, when using setInterval or setTimeout inside a useEffect hook, it's important to clear these timers on unmount to prevent them from running indefinitely and causing unexpected behavior.

import React, { useState, useEffect } from 'react';

export default function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // Start interval on mount
    const intervalId = setInterval(() => {
      setSeconds((prevSeconds) => prevSeconds + 1);
    }, 1000);

    return () => {
      // Clear the interval on unmount
      clearInterval(intervalId);
    };
  }, []);

  return <div>Seconds passed: {seconds}</div>;
}

Proper cleanup helps prevent memory leaks and ensures that our components behave as expected. Whether adding event listeners or setting intervals, always remember to clean up when the component unmounts.