Amir Khan
Back to Blog
3 min read

Understanding the useEffect Cleanup

ReactHooksJavaScript

In React, useEffect is the primary tool for managing side effects like timers, event listeners, and data fetching. However, these effects often need to be "undone" to keep the application running smoothly. This is where the cleanup function comes in.

How the cleanup function works

The cleanup function is the function you optionally return from useEffect. React uses it to reset the environment before the component unmounts or before the effect runs again.

To understand the timing, think of it as a cycle:

  1. Cleanup: React runs the cleanup function from the previous render (if dependencies changed).
  2. Setup: React runs the effect logic for the current render.

This cycle ensures that side effects don't accumulate. For example, if you add a scroll listener, the cleanup function ensures the old one is removed before a new one is added.

Managing intervals effectively

Intervals are a common use case for cleanup. If we don't clear an interval, it will continue to run even after the component is gone.

useEffect(() => {
  const timer = setInterval(() => {
    setCount((prev) => prev + 1);
  }, 1000);

  // Clearing the interval on unmount or re-render
  return () => clearInterval(timer);
}, []);

Using the functional update (prev => prev + 1) allows us to keep the dependency array empty ([]). This means the interval starts once and only cleans up when the component unmounts.

Preventing race conditions in data fetching

When fetching data, network requests can sometimes finish in a different order than they were started. This can lead to "race conditions" where the wrong data is displayed.

We can manage this by using a boolean flag in our cleanup:

useEffect(() => {
  let active = true;

  async function fetchData() {
    const data = await fetchUser(userId);
    if (active) setUserData(data);
  }

  fetchData();

  return () => {
    active = false; // Mark the previous request as inactive
  };
}, [userId]);

When userId changes, the cleanup sets active = false. This ensures that even if the previous request finishes late, it won't update the state with outdated data.

When to use alternatives

While useEffect is powerful, some side effects are better managed with specialized hooks. For subscribing to external data stores, useSyncExternalStore is often a better choice as it handles concurrent rendering and subscription logic more automatically.

Quick Summary

  • Timing: Cleanup runs before the effect re-runs and on unmount.
  • Purpose: It’s used to remove listeners, clear timers, and cancel requests.
  • Best Practice: Always ask if your side effect needs to be "undone" to prevent unnecessary resource usage.

For more details, you can refer to the Official React Documentation.