DEV Community

Tikam Singh Alma
Tikam Singh Alma

Posted on

How fixing memory leaks in React can improve overall performance.

What is memory leaks in react app and what is the root problem?

Memory leaks are very common and can be very problematic if not handled properly.
A memory leak is a commonly faced issue when developing React applications. It causes many problems, including:

  • affecting the project's performance by reducing the amount of available memory
  • slowing down the application
  • crashing the system
  • Reduces the overall app latency and performance

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 un-referenced 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.

A memory leak is a commonly faced issue when developing React applications. A memory leak, in React, is a type of resource leak that occurs when an application incorrectly manages memory allocations. That memory, which is not needed anymore, is not released for other processes to use. A memory leak may also happen when an object is stored in a memory, but cannot be accessed by the running code.

Once you have a memory leak, you will get a similar error in console log

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

memory leaks

What causes a memory leak?

A memory leak appears when React applications created subscriptions that were made when a component was mounted and did not unsubscribe them when those components were unmounted.

These subscriptions could be:

  • DOM Event listeners
  • WebSocket subscriptions
  • Requests to an API

Here is a normal scenario that causes this memory leak issue:
Basically, when doing multiple callbacks to api or functions using mounted functions and not unmounting it causes memory leaks.

  • The user performs an action that triggers an event handler to fetch data from an API.
  • After that, a user clicks on a link, which navigates to another page before completing step #1.
  • Now, the first action completes and passes the data retrieved from the API and calls function, which updates the state.

Since the component was unmounted and function is being called in a component that is no longer mounted, it causes memory leak issue -- and in the console, you'll get a warning.

Why should you clean-up or fix the memory leaks?

Memory leaks are commonly faced issues when developing React applications. It causes many problems, including:

  • Affecting the project’s performance by reducing the amount of available memory
  • Slowing down the application
  • Refreshing the page randomly
  • Crashing the system
  • Overloading database with huge amounts of queries

What is solution

Basically, we need to remove subscriptions when the component unmounts.
Having understood the problem,what we have to do is, to unmount the compoment or you can cancel the function as soon as API or function calls fetches data or done it's works.
Situation is cancel the request the moment our component unmounts.

  • Cancel the function call or API call using Abortcontroller
  • using properly coding

Using Abortcontroller?

What is abortcontroller?
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.

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.

const Cars = () =>  {
 const [carList, setCarList] = useState([]);
 useEffect( () => {
   let abortController;
   (async () {
     abortController = new AbortController();
     let signal = abortController.signal;
     const { data } = await axios.get('API_ENDPOINT', { signal: signal });
     setCarList(data)
   })();
   return () => abortController.abort();
  }, []);

  return (
   <>...</>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.

Most of the time this solution don't work.Yes,I have tried this, so the next solution worked 100% everytime.

Using proper return function?

As react developers, we use useEffect() 80% of the time to call API and callback functions.
useEffect() is two edge sword it is used to used to cut propery but it also hurts the holder if not used proplery.

export default function Level02() {
  console.log('renderLevel02');
  const [count, setCount] = useState(0);
  useEffect(() => {
    setInterval(() => {
      setCount(count + 1);
    }, 500);
  });
  return <div>Level 2: count => {count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Most side-effects happen inside useEffect. This code also has a huge resource leak and is implemented incorrectly. Default behavior of useEffect is to run after every render, so new interval will be created every time count changes.

cleanup the above function

useEffect(() => {
    const interval = setInterval(() => {
      setCount(count + 1);
    }, 300);
    return () => clearInterval(interval);
  }, []);
Enter fullscreen mode Exit fullscreen mode

To prevent resource leaks, everything must be disposed when lifecycle of a hook ends. In this case returned function will be called after component unmounts.

This code does not have resource leaks, but is implemented incorrectly, just like previous one.

use "count" dependency

useEffect(() => {
  const interval = setInterval(() => {
    setCount(count + 1);
  }, 500);
  return () => clearInterval(interval);
}, [count]);
Enter fullscreen mode Exit fullscreen mode

Giving array of dependencies to useEffect will change its lifecycle. In this example useEffect will be called once after mount and every time count changes. Cleanup function will be called every time count changes to dispose previous resource.

functional updates for useState

useEffect(() => {
  const interval = setInterval(() => {
    setCount(c => c + 1);
  }, 500);
  return () => clearInterval(interval);
}, []);
Enter fullscreen mode Exit fullscreen mode

In previous example we ran useEffect on each count change. The was necessary because we needed to have always up-to-date current value.

useState provides API to update previous state without capturing the current value. To do that, all we need to do is provide lambda to setState .

This code works correctly and more efficiently. We are using a single setInterval during the lifecycle of a component. clearInterval will only be called once after component is unmounted.

Now this is the perfect cleanup function.

Thanks for reading!

Top comments (0)