cft

Avoid Memory Leaks in your React App by canceling API calls

Cancel API calls using AbortController with the Fetch API to avoid memory leak warnings in your console


user

Eyuel Berga Woldemichael

2 years ago | 4 min read

Surely you have encountered this warning while working on a React app. Even though the JavaScript language has an automated memory management mechanism, it is still possible for memory leaks to happen if we are not careful of how we implement our programs. The same applies to a React application. Memory leaks are not a good sign and should always be avoided.

The most easily overlooked cause for memory leaks would be improper handling of API calls. Because calls to an API are asynchronous, it is possible for the component that initiated the task to be unmounted before the request finishes. This can happen for instance, when a user goes to another page while the current page has not finished loading.

A Demo

To demonstrate, let's use a simple React app that has two components, Home and About, and also a button to toggle between the two. The Home component fetches blog post data from an API that we’ll set up using json-server. We will also delay our server’s response time to 3 seconds so that we have enough time to switch between the two components before the API request completes.

import { useState } from "react";

import Home from "./Home";

import About from "./About";

function App() {

const [isHome, setIsHome] = useState(true);

return (

<div>

<header>

<button

onClick={() => {

setIsHome(!isHome);

}}

>

{isHome ? "About Page" : "Home Page"}

</button>

</header>

{isHome ? <Home /> : <About />}

</div>

);

}

export default App;

view raw

In our App.jsx we have a button that toggles the isHome state and conditionally renders either the Home or the About component.

import { useEffect, useState } from "react";

import "./Home.css";

function Home() {

const [posts, setPosts] = useState([]);

const [loading, setLoading] = useState(false);

useEffect(() => {

setLoading(true);

fetch("http://localhost:3001/posts")

.then(response => response.json())

.then(json => {

setPosts(json);

setLoading(false);

})

.catch(error => {

window.alert(error);

setLoading(false);

});

}, []);

return (

<div>

<h1>Home Page</h1>

{loading && <p className="loader">Fetching posts please wait...</p>}

{!loading &&

posts.map(({ title, body, id }) => (

<div key={id} className="card">

<h3>{title}</h3>

{body}

</div>

))}

</div>

);

}

export default Home;

The Home component fetches the blog post data from our API server during the initial render.

function About() {

return (

<div>

<h1>About this Demo</h1>

<p>

This demo is intended to show how to avoid memory leaks in React

Applications by properly canceling API calls in useEffect cleanup

function

</p>

</div>

);

}

export default About;

The About component just contains some text to be rendered in the DOM.

We also need to add json-server and configure it in our package.json file with 3 seconds response delay.

{

"scripts":{

...

"server": "json-server --watch db.json --port 3001 --delay 3000"

}

}

Now, if we run our project and click on the “About Page” button while loading is in progress, we will get a memory leak warning on the console.

Memory leak warning shows up on the console

The Solution

A way to fix this issue is to cancel the API request when the useEffect cleanup function is called. The preferred way of canceling a request would be to use the AbortController as described in the Web API documentation. The AbortController will allow us to abort the web request by passing the AbortSignal to the fetch method and then calling abort before the component is unmounted.

We need to make slight modifications to our Home component to avoid the memory leak problem. We initialize a new AbortController instance and pass the signal from the controller to our fetch method as a signal property. Then we call controller.abort on the return function of our useEffect hook.

useEffect(() => {

setLoading(true);

const controller = new AbortController();

fetch("http://localhost:3001/posts", { signal: controller.signal })

.then(response => response.json())

.then(json => {

setPosts(json);

setLoading(false);

})

.catch(error => {

window.alert(error);

setLoading(false);

});

return () => {

controller.abort();

};

}, []);

Now, if we try the same thing as before and switch to the About component while loading is in progress the memory leak warning will not show up in the console.

Conclusion

Well, that’s it for this article. Hope you enjoyed reading it. You can find the source code for the demo here.

Upvote


user
Created by

Eyuel Berga Woldemichael

I am a Software Developer currently based in Addis Ababa, Ethiopia. I am passionate about Web and Mobile Development.


people
Post

Upvote

Downvote

Comment

Bookmark

Share


Related Articles