DEV Community

Cover image for Ultimate Guide on React cleanup function
jabo Landry
jabo Landry

Posted on

Ultimate Guide on React cleanup function

If you've been using React for some time you may be familiar with the term "cleanup function" when working with side effects, it may be confusing at first or sometimes you don't make sense out of it.

In this guide I will explain the use of cleanup function so you can start using it with confidence and clarity.

Table of Contents

What is a cleanup function

A cleanup function in React is the function you return from inside a useEffect, React calls cleanup function when the component unmounts or before the effect re‑runs, so you can stop timers, cancel requests, or remove event listeners.

In development, StrictMode intentionally mounts, unmounts, and re‑mounts components once to check that your cleanup logic works correctly. This helps catch bugs with side effects early.

Not every effect needs cleanup — only those that start something ongoing (like a subscription or fetch).
Without cleanup, you risk inconsistent UI updates or memory leaks.

Why use a cleanup function (with example)

Let's take an example of fetching data from an API and have the pagination in place and a select menu to select where you to start fetching data.

First create a function to fetch the comments using a dummy API (https://dummyjson.com/comments) that receives an argument of where to start while fetching the data.

export default function CleanUpComponent(start) {
  function fetchComments(start) {
    return new Promise((resolve) => {
      setTimeout(() => {
        fetch(`https://dummyjson.com/comments?skip=${start}`)
          .then((res) => {
            resolve(res.json());
          })
          .catch((err) => Error(err));
      }, 3000);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

In the function you return a new promise and then we add setTimeout function to wait for three (3) second before returning the response,

Note: "that the setTimeout here is just to simulate a slow network for demonstration purposes — in a real app you wouldn't need it".

why setTimeout? to observe the importance of having the cleanup function we need to delay the time it may take to respond for the users with poor network signals or in any case where we the network may be slow for some reason.

And then create a state that will track the selected starting id when fetching and then the state to store and display the fetched data.

import { useEffect, useState } from "react";

export default function CleanUpComponent() {

  const [dataDisplay, setDisplay] = useState([]);
  const [loading, setLoading] = useState(true);
  const [start, setStart] = useState(0);

  function fetchComments(start) {
    return new Promise((resolve) => {
      setTimeout(() => {
        fetch(`https://dummyjson.com/comments?skip=${start}`)
          .then((res) => {
            resolve(res.json());
          })
          .catch((err) => Error(err));
      }, 3000);
    });
  }

  useEffect(() => {
    setLoading(true);

    fetchComments(start)
      .then((res) => setDisplay(res["comments"]))
      .catch((err) => console.error(err))
      .finally(() => setLoading(false));
  }, [start]);
}
Enter fullscreen mode Exit fullscreen mode

Inside the component then add a useEffect to perform the fetch operation and synchronize the application with you’re react application, and we can display the data on the screen.

  return (
    <>
      <select
        name=""
        id=""
        onChange={(e) => setStart(Number(e.currentTarget.value))}
      >
        <option value="select an option">--select an option to start with</option>
        <option value="5">5</option>
        <option value="10">10</option>
        <option value="15">15</option>
      </select>

      {loading && <p>loading....</p>}

      {!loading &&
        dataDisplay.slice(0, 6).map((element) => {
          return (
            <p key={element.id}>
              {element.id}: {element.body}
            </p>
          );
        })}
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

This approach works but there is an issue in the above snippet, if you try choosing a different number in the option menu to start with fetching while the previous fetching is still going on.

The UI will show inconsistent data which can cause frustration to user using the site.

If you observe the above demo example you can see that we first select 10 as the starting point while it is still fetching,

we change the number to start at 5 and after a few seconds in our case 3 seconds remember we’ve used it as a delay for the timer to call a function,

the screen shows the data from 11 but then suddenly that UI changes to data from 6.

This kind of data inconsistency is known as a race condition. A race condition occurs when multiple async requests finish out of order, and an outdated request updates the UI after a newer one.

if you were reading through fetched data and suddenly the UI changes the data that a big frustration and a red flag to a site.

Cause

The cause for this kind of behavior is that when fetching the useEffect will send the first request while it is still going on we fire another fetch and then the first request will resolve after a few seconds the last request will also resolve and updates the existing state and component re-renders which updates the UI.

Solution:

The solution is to use a cleanup function in the useEffect to clear out any previous running task and instead continue with the current requested request, the cleanup function is defined by returning function that will clear out the previous request.

For this example, use the abortController to cancel any previous running task in the background, change the function to fetch to receive the second argument which will be a controller which will use to cancel the previous request.

if you are not familiar with abortController you can read more about it using this AbortController - Web APIs | MDN

 function fetchComments(start, controller) {
    return new Promise((resolve) => {
      setTimeout(() => {
        fetch(`https://dummyjson.com/comments?skip=${start}`, {
          signal: controller.signal,
        })
          .then((res) => {
            resolve(res.json());
          })
          .catch((err) => Error(err));
      }, 3000);
    });
  }
Enter fullscreen mode Exit fullscreen mode

There is the signal option on fetch API that helps to abort or cancel request based on the signal provided, and abortController we have a signal property that ties together the current fetch call signal with the abortController and then abort the previous request in the cleanup function.

useEffect(() => {
     //creation of abortcontroller.
    const controller = new AbortController();

    setLoading(true);
    fetchComments(start, controller)
      .then((res) => setDisplay(res["comments"]))
      .catch((err) => console.error(err))
      .finally(() => setLoading(false));

   // cleanup function to abort the previous request.
    return () => {
      controller.abort();
    };
  }, [start]);
Enter fullscreen mode Exit fullscreen mode

Create a controller variable to hold the abortController instance and the pass it in the function you have created to fetch the data and in the cleanup function call the abort() on the controller instance to stop the request that was not finished.

Now if we try switching the options instantly the data will be consistence and they will be no race conditions.

I have added logs before the cleanup and after to show you how the cleanup works, if you want you can add a console log before the abort and another one after aborting the request.

now if you were to switch to another number while there are some fetching that are going on they will be aborted and then fetch the latest request now you can see that there are no flash data changes like we had before.

Summary

The cleanup function is essentially used to remove and clear side effects tasks that are still running in the background when the component is unmounted, or the effect is going to re-run.

To avoid problems like race conditions, memory leaks and many more issues that can be caused the side effects fetching.

Top comments (0)