How to fix the memory leak in reaction applications

by SkillAiNest

Have you ever seen that you are getting more slowly in using your react application? This can be the result of a memory leak. Memory leak is a common performance problem in reacting applications. They can slow down your request, crash your browser, and disappoint users.

In this tutorial, you will learn what is due to memory leaks and how to fix them.

The table of content

Provisions

Before proceeding, make sure you have:

  • Javascript, reaction, and reaction hooks

  • A understanding of the event, timer, and unprecedented calls

  • Setup of a reaction development.

If you do not have a rect development setup, you can go towards Memory leak repo. Run the commands below to configure it:


git clone 


cd freecodecamp-fix-memory-leak.git


pnpm install


pnpm dev

What are the memory leaks in the reaction?

In the JavaScript, the memory leak occurs when an application allocates memory but fails to release it. This happens even after memory is not needed.

In response, the leakage of memory occurs when a component produces resources but does not remove them when they suffer without them. These resources can be an event listener, timer, or subscription.

Since the user is longer in the application, these resources are collected. The application is used more RAM because of this collection. This will eventually cause many problems:

  • A slow request

  • Browser crashing is happening

  • A poor experience of the user

For example, when a component increases, a component can form a “size” maker, but when it is suffering, it forgets to remove it. This produces memory as the user remains tall in the application and changes the size of the screen.

When does a component not happen?

When a component is not present in the Dome, there is no component. It can be if:

  1. A user navigates away from the page.

     
       "/posts" element={} />
       "/dashboard" element={} />
     
    

    The component of the dashboard will be immediately unintentional when the user is navigated /dashboard On any other way in the application.

  2. A component is presented conditionally.

     function App() {
       const (show, setShow) = useState(true);
    
       return 

    {show && }

    ; }

    When the non -disconnected show Goes wrong.

  3. One component key changes.

     function App() {
       const (key, setKey) = useState(Date.now());
    
       return (
         <>
           
           
         >
       );
     }
    

    component will unmount every time the key changes. Also note that a new component will mount each time the key changes.

Common Causes Of Memory Leaks And How To Fix Them

As said earlier, there will be a memory leak when resources are not removed after a component unmounts. React useEffect Allows you to return a function that will be called when a component is disconnected.

useEffect(() => {
  return () => {
    
  };
}, ());

You can clear any created resources in this looted function. We will pass through some of these resources to clean up.

Listener

The audience’s audience remains intact if they are not removed after any ingredients. View the code below:

import { useEffect, useState } from "react";

const EventListener = () => {
  const (windowWidth, setWindowWidth) = useState(0);

  useEffect(() => {
    function handleResize() {
      const width = window.innerWidth;
      console.log("__ Resizing Event Listerner __", width);
      setWindowWidth(width);
    }

    window.addEventListener("resize", handleResize);
  }, ());

  return 

Width is: {windowWidth}

; }; export default EventListener;

We do not remove the listeners of the unauthorized program on the non -mount, so each Mount adds a new listener. This failure to clean up memory leaks.

GIF shows that many 'sizes' event listeners increase one ingredient each time.

As shown in the aforementioned GIF, whenever we change the size of the window width, we log in the console width. We still log in the same information after the incorrect information of the ingredients. Also, when we check the “event listeners” tabs, whenever we lose the component, the number of listeners increases by only 2 instead of 1.

We see two audiences when the component increases as the reaction uses hard work in development. This helps see side effects in development mode. That is why when we mount the component, the audience increases by 2.

We need to remove the listeners in our cleaning function to fix this memory leak.

useEffect(() => {
  

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, ());

The cleaning function runs when the component increases. As a result, it removes the listeners of our event and prevents memory leaks.

GIF shows a 'size' event listeners that are removed from eliminating the ingredient.

Consider this time, when we hide the component, nothing is shown in the console. Also, when we hid the component (non -mounted) when we showed it (mount), the listener of the size program was reduced to 0.

Timer

Timers like setInterval And setTimeout If the ingredients are not cleaned after being disconnected, the memory can also cause leakage. Look at it:

const Timers = () => {
  const (countDown, setCountDown) = useState(0);

  useEffect(() => {
    setInterval(() => {
      console.log("__ Set Interval __");
      setCountDown((prev) => prev + 1);
    }, 1000);
  }, ());

  console.log({ countDown });

  return 

Countdown: {countDown}

; };

The intermittent response will continue even after hiding or eliminating the ingredient.

Note that, in the reaction 18+, the reaction ignores the refreshment of the state when a component is already uncomfortable.

The GIF shows a countdown timer component that keeps running and updating the state after the Dom is disconnected.

In GIF, we have noticed that whenever we hide the component/disconnect, the “__ external effect” stops displaying. But the wire, “interval __”, appears every time.

We can fix it using a cleaning function. All timers (setIntervalFor, for, for,. setTimeout) Return a unique timer ID that we can use to clean the timer after eliminating the ingredient.

const (countDown, setCountDown) = useState(0);
useEffect(() => {
  const timer = setInterval(() => {
    console.count("__ Interval __");
    setCountDown((prev) => prev + 1);
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, ());

Now we save the timer ID and use this ID to clear the interval when the ingredient is disconnected. Applies to the same method setTimeout; Save the ID and clean it with clearTimeout.

GIF shows an opposite timer component that refuses to run and update the state.

Subscripts

When a component subscribes external data, it is always appropriate to subscribe to them after the ingredient is unambiguous. Most data sources return the callback function to subscribe to such data. Take the Firebase for example:

import { collection, onSnapshot } from "firebase/firestore";
import { useEffect } from "react";

const Subscriptions = () => {
  useEffect(() => {
    const unsubscribe = onSnapshot(collection(db, "cities"), () => {
        
        
    });
  }, ())

    return 

Subscriptions

; }; export default Subscriptions;

onSnapshot From the function firebase/firestore Receive real -time updates from our database. It returns a callback function that prevents DB’s updates from listening. If you fail to call this function, our app keeps listening to these refreshments until it does not need them.

useEffect(() => {
  const unsubscribe = onSnapshot(collection(db, "cities"), () => {
    
    
  });

  return () => {
    unsubscribe();
  };
}, ());

Call unsubscribe() The returning function means that we are no longer interested in hearing data updates.

Async operations

A common mistake is not to cancel an API call when it is not required. It is a waste of resources that when the ingredients of the component are gone, the API call is moving. The reason for this is that unless the promise is resolved, the browser continues to hold references in memory. Look at this example:

import { useEffect, useState } from "react";

interface Post {
  id: string;
  title: string;
  views: number;
}

const ApiCall = () => {
  const (loading, setLoading) = useState(false);
  const (error, setError) = useState("");
  const (data, setData) = useStatenull>(null);

  useEffect(() => {
    const getTodos = async () => {
      try {
        setLoading(true);

        console.time("POSTS");
        const req = await fetch("");
        const res = await req.json();
        console.timeLog("POSTS");
        setData(res.posts);
      } catch (error) {
        setError("Try again");
      } finally {
        setLoading(false);
      }
    };

    getTodos();
  }, ());

  return (
    
"2rem" }}>

ApiCall Component

{loading ? (

Loading...

) : error ? (

{error}

) : data ? (

Views: {data(0).views}

) : null}
); }; export default ApiCall;

This component brings a list of posts from our server immediately. This API changes UI based on call condition:

  • This loading text shows when you click on the button.

  • If the API fails, it shows a mistake.

  • If the API is successful, it shows the data.

We have a simple server that returns the list of posts. The problem with the server is that it takes three seconds to return the list of posts.

What happens when a user comes to this page but decides to go before three seconds? (We imitate the ingredient button and leave the page.)

GIF shows a component that continues with no API call.

As you can see, the browser still refers to the application, though it is no longer needed.

The appropriate way to fix this is that when the component is not confronted, the application is to be canceled. We can use it abortcontroller. We can use abort How to cancel the application before completion, thus issues memory.

import { useEffect, useState } from "react";

interface Post {
  id: string;
  title: string;
  views: number;
}

const ApiCall = () => {
  

  useEffect(() => {
    const controller = new AbortController();

    const getTodos = async () => {
      try {
        

        const req = await fetch("", {
          signal: controller.signal,
        });

        
      } catch (error) {
        if (error instanceof Error && error.name === "AbortError") {
          console.log("Request was cancelled");
          return;
        }

        setError("Try again");
      } finally {
        setLoading(false);
      }
    };

    getTodos();

    return () => {
      controller.abort();
    };
  }, ());

  return (
    
"2rem" }}>

ApiCall Component

{}
); }; export default ApiCall;

When the ingredient increases, we created a controller to track our API application. We then connect the controller to our API application. If the user leaves the page within three seconds, our cleaning function cancels the application.

We can see the result in GIF below:

GIF shows that an API call has been canceled after its component was dismissed.

Most production reacts use external libraries to bring API. For example, Reacting queries Allows us to cancel processing promise:

const query = useQuery({
  queryKey: ("todos"),
  queryFn: async ({ signal }) => {
    const todosResponse = await fetch("/todos", { signal });
    const todos = await todosResponse.json();

    return todos;
  },
});

Conclusion

Memory leaks can significantly affect your reacting application performance and user experience. When you get into a component, you can prevent these problems by cleaning the resources properly. In summary, always remember:

  • Remove the event with listeners removeEventListener.

  • Clean timer with clearInterval And clearTimeout.

  • Eliminate membership from external data sources.

  • Cancel the API requests using AbortController.

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro