Memory Leaks, How to avoid them in a React App.
Tags: react, javascript, webdev, tutorial
Published: 2022-01-08 09:58:39

## What is a memory leak?

According to [Wikipedia](https://en.wikipedia.org/wiki/Memory_leak), 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](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)), 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1641496893441/UnKO065qK.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

```jsx
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>
        </>
    )
}
```

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](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)**

According to [MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), 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:

```jsx
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>
        </>
    )
}
```

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❤❤