Skip to main content
React useEffect Cleanup Function
6 min read

React useEffect Cleanup Function

Introduction

The useEffect hook is a popular hook that combines the functionality of componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods of React class components. The hook helps developers handle side effects in their programmes. Side effects may involve receiving data from an API, configuring event listeners, or subscribing to a WebSocket. All of these activities might influence the state of your application and may cause memory leaks if not taken care of.

The useEffect hook comes with a cleanup function that cleans up effects to prevent memory leaks, which in turn improves the performance of the application. In this article, we will explain the cleanup function of the useEffect hook.

What are “side-effects”?

A "side effect" is an operation that impacts something other than the function being executed. In React, a side effect occurs when we impact something outside the scope of React in our React components. A side effect could be retrieving data from a remote server, reading or writing to local storage, configuring event listeners, or creating a subscription. These side effects could also occur when a button is clicked, a form is submitted, or a component is mounted and unmounted.

Why cleanup “side-effects”?

Cleaning up side effects in React is a method of removing side effects that are no longer required. This, in turn, avoids memory leaks, which occur when your program or application attempts to perform an unnecessary operation or update a state memory location that no longer exists.

What is the useEffect cleanup function?

The useEffect cleanup function is a return function within the useEffect hook.

import React, { useEffect } from "React";

useEffect(() => {
// Your effect
return () => {
// Cleanup
};
}, []);

What does useEffect function do?

The function allows React developers to stop side effects that do not need to be executed before our component is unmounted.

For example, you have a React component that performs a certain action in a setTimeout. Every time the component is shown, it runs the setTimeout function and performs the action in the timeout on that component. Assuming You decided to navigate away from this component and into another portion of the app. The component is no longer being rendered, thus there is no need to leave the setTimeout function running in the background. However, the setTimeout function is still running in the background.

This is where the useEffect cleaning function comes in. In the cleanup function, we can simply add a clearTimeout which clears the timeout when the component is unmounted (i.e., no longer rendered).

When to use the useEffect cleanup function

There are various scenarios which will prompt the use of the useEffect cleanup function. They are as follows:

Fetch requests

When initiating an API request in a component, it is important that we also account for a way to abort the request when the component is unmounted or re-rendered. There are several methods for canceling fetch request calls; we can use the native fetch AbortController or Axios's AbortController (If you are utilizing Axios as our API Client).

To utilize AbortController, first, create a controller with the AbortController() constructor. When our fetch request is initiated, we pass signal as an option in the request's options object.

This associates the controller and signals with the fetch request, allowing us to cancel the request call at any time using the AbortController.abort() method. We will then add the AbortController.abort() method to our useEffect cleanup function to ensure that the request is aborted when the component is unmounted or re-rendered.

Examples of the useCases are displayed below:

Fetch example:

useEffect(() => {
//create the abort controller
let controller = new AbortController();

(async () => {
try {
const response = await fetch(APIEndpoint, {
// attach the controller to the request
signal: controller.signal,
});
// add the success response to a state value
} catch (e) {
// Handle the error
}
})();
//abort the request when the component umounts
return () => controller?.abort();
}, []);

Axios example:

useEffect(() => {
// create a controller
let controller = new AbortController();

(async () => {
try {
const response = await axios.get(APIEndpoint, {
// attach the controller to the request
signal: controller.signal,
});

// add the success response to a state value
} catch (e) {
// Handle the error
}
})();
//abort the request when the component umounts
return () => controller?.abort();
}, []);

Timeouts

For timeouts, you can use the setTimeout(callback, timeInMs) timer function in the useEffect hook, followed by the clearTimeout(timerId) function in the cleanup function. This guarantees that the timer is cleared when the component is unmounted.

An example of this is displayed below:

useEffect(() => {
let timerId = setTimeout(() => {
// perform an action like state update
timerId = null;
}, 5000);

// clear the timer when component unmouts
return () => clearTimeout(timerId);
}, []);

Intervals

The setInterval(callback, timeInMs) function can be declared in the useEffect hook, and the clearInterval(intervalId) function can be added to the cleanup function to handle intervals. By doing this, you can be sure that the timer will stop when the part is unmounted.

An example of this is displayed below:

useEffect(() => {
let intervalId = setInterval(() => {
// perform an action like state update
intervalId = null;
}, 5000);

// cleanup the interval when component unmounts
return () => clearInterval(interval);
}, []);

Event Listeners

For event listeners, you can define them in the useEffect callback by attaching the addEventListener function to the element, and then cleanup the listeners by attaching the removeEventListener function to the element on the useEffect cleanup function. This guarantees that the listener is removed from the element when the component is mounted.

A simple usage of this is displayed in the example below, which is a custom hook for handling click events that occur outside of a certain DOM element, such as a div.

const useOutsideClick = (callback: () => void) => {
const ref = useRef<HTMLElement>();

useEffect(() => {
const handleClick = (event: Event) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback();
}
};

document.addEventListener("click", handleClick, true);

return () => {
document.removeEventListener("click", handleClick, true);
};
}, [callback, ref]);

return ref;
};

Web sockets

For WebSockets, When you create a WebSocket connection in a component, you can close it when the component unmounts by including the socket.close() method in the cleanup function.

An example of this is displayed below:

useEffect(() => {
const ws = new WebSocket(url, protocols);
// perform socket related actions

// cleanup web socket when component unmounts
return () => ws.close();
}, []);

Conclusion.

In this article, we learned about side effects and how they create memory leaks and unwanted behaviors in React components. We also looked at how to use the useEffect cleanup function to fix memory leaks. Understanding how and when to repair memory leaks with the cleanup function is a significant step toward becoming a better React developer.