DEV Community

Cover image for Memory Leaks, How to avoid them in a React App.
Lena Jeremiah
Lena Jeremiah

Posted on

Memory Leaks, How to avoid them in a React App.

What is a memory leak?

According to Wikipedia, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory that is no longer needed is not released. A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code.

Simply put, a memory leak is said to occur whenever inaccessible or unreferenced data exists in memory. Nowadays, many modern programming languages have techniques for clearing out data that is no longer needed, garbage collection, but it turns out there are other not-so-popular errors which can expose your React app to memory leaks and, to a great extent, reduce the performance of your app.

Let's look at some causes of memory leaks.

Causes of Memory Leaks in a React Application

memory leak.PNG

Memory leaks in React applications are primarily a result of not cancelling subscriptions made when a component was mounted before the component gets unmounted. These subscriptions could be a DOM Event listener, a WebSocket subscription, or even a request to an API.

The first two are not too much of a challenge, as we can easily remove an event listener or unsubscribe from the WebSocket before the component gets unmounted. But the last one might require a little bit of extra work.

A typical React workflow

import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';

const MyCompany = function() {
    const [ company, setCompany ] = useState(null);
    useEffect(() => {
        (async () {
             const { data } = await axios.get(
                 'https://random-data-api.com/api/company/random_company'
             );
             setCompany(data);
        })();
    }, []);

    return (
        <>
            <pre>{JSON.stringify(company, null, 3)}</pre>
            <Link to = '/anotherpage'>Another Interesting Page</Link>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we have a simple component MyCompany which when mounted, makes a request to get a random company and sets value of company state to the value gotten from the API.

The Problem

Assuming our user has a very slow internet connection and then decides to leave the current page for another interesting page, the request would have already been made and our browser would be expecting a response, which when received, would lead us to call setState on a component that's no longer mounted.

Aside from setting state, we would now have unimportant data in our app with no means of accessing them. This process is repeated multiple times while the user uses the app, filling up useful memory with useless and inaccessible data and leading to serious performance issues.

We've seen the problems and I believe you understand, now let's look at how to solve this problem.

The Way Forward: AbortControllers

Having understood the problem, what we'd do to salvage the situation is cancel the request the moment our component unmounts, ensuring we don't get any data from the API.

So, how do we cancel requests? AbortControllers

According to MDN, the AbortController represents a controller object that allows you to abort one or more Web requests as and when desired. That's quite explanatory!!

AbortControllers are created with the new AbortController() syntax, initializing an instance of the AbortController class. Every AbortController object has a read-only signal property which is passed into requests, and an abort() method which is whenever you want to cancel a request.

Now using AbortControllers, our code should look like this:

import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';

const MyCompany = function() {
    const [ company, setCompany ] = useState(null);

    useEffect(() => {
         let abortController;
        (async () {
             abortController = new AbortController();
             let signal = abortController.signal;    

             // the signal is passed into the request(s) we want to abort using this controller
             const { data } = await axios.get(
                 'https://random-data-api.com/api/company/random_company',
                 { signal: signal }
             );
             setCompany(data);
        })();

        return () => abortController.abort();
    }, []);

    return (
        <>
            <pre>{JSON.stringify(company, null, 3)}</pre>
            <Link to = '/anotherpage'>Another Interesting Page</Link>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

Now, when our user navigates to a new page, our AbortController cancels the request and we don't have to worry about having data leaks.

NOTE: Calling abortController.abort() after the request has been completed doesn't throw any errors. The abortController simply does not take any action on an already complete request.

Using AbortControllers in your web apps can help improve performance and prevent memory leaks, so it's something you should actually use.

Thanks for reading❤❤

Top comments (19)

Collapse
 
shivams136 profile image
Shivam Sharma

I have created a wrapper named as advanced-promise for promises including fetch which provides aborting and some other features as well. You can try it and we can improve it together.

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

I just checked the package on GitHub, it makes total sense. I'd try to use it in one of my projects soon.

I'd sure love to work on it with you.

Collapse
 
shivams136 profile image
Shivam Sharma

Thanks a lot, It's my first ever npm package so it may not be that well written... I'm sure with your experience it can be improved 😊

Thread Thread
 
jeremiahjacinth13 profile image
Lena Jeremiah

Nice, I've never written one yet. I'd like to collaborate with you on this. You can email me at jeremiahlena12@gmail.com

Thread Thread
 
jeremiahjacinth13 profile image
Lena Jeremiah
Thread Thread
 
rprtg profile image
rprtg

رپورتاژ آگهی (رپورتاژ خبری) در واقع اطلاعیه‌ای است یک رپورتاژ که از سوی واحد روابط عمومی با هدف اطلاع‌رسانی اتفاقات کسب و کار به مردم در رسانه‌های مختلف و انتشارات مرتبط و هدفمند منتشر می‌‌شود

Collapse
 
midoukh profile image
Ahmed Khelili

This is exactly what I needed, thanks a lot 🙏

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

I'm happy you found it helpful😊😊

Collapse
 
stephcrown profile image
Steph Crown

Nice article.

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

Thanks for reading bro

Collapse
 
dro1 profile image
Seun Taiwo

Very explanatory article. Thanks

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

Thanks for reading bro

Collapse
 
framitdavid profile image
David Øvrelid • Edited

First of all thanks for sharing. 👏

But I do not understand how your example is providing memory leak while setting the company after the component itself is unmounted.

Where in the memory will the variable company be stored when the component is unmounted? Would the company variable have any references at all? If not I believe the garbage collector is going to collect it?

Can you please explain why and how your example is providing the leak? 🤔

I would understand the memory leak if you had a setInterval or was subscribing to streams like WebSockets. But within your case where you have promise which resolve once, I believe the GC collects it.

Thanks in advance! :)

Collapse
 
dinniej profile image
Nguyen Ngoc Dat

Nice, i hardly consider the network issues when working on a product, and i bet most of us didn’t either. Great article

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

Yeah... I also didn't put much consideration to it until I found out about AbortControllers and decided to share. I'm glad you enjoyed reading it

Collapse
 
daehyeonmun2021 profile image
Daehyeon Mun

Very interesting article. Thanks for sharing your knowledge :D

Collapse
 
jeremiahjacinth13 profile image
Lena Jeremiah

Thank you for reading @daehyeonmun2021

Collapse
 
tasin5541 profile image
Miftaul Mannan

Is this still relevant according to the warning removal from react v18 and the discussion in the thread?

github.com/facebook/react/pull/22114

Collapse
 
ammarsiddiqi profile image
AmmarSiddiqi

Thanks Lena for sharing this article. Really Helpful. 😊