Memory leak examples in React.js.

NISHANK KUMAR
5 min readJun 12, 2023

In React.js, a memory leak can happen when objects or resources that are no longer required are still being held by the application, preventing their proper garbage collection. As a result, memory usage gradually grows, leading to diminished performance over time.

Example-1:

import React, { useState, useEffect } from 'react';
const MemoryLeakExample = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};
fetchData();
return () => {
// Clean up function
setData([]); // Reset data to an empty array
};
}, []);
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
export default MemoryLeakExample;

In this example, we have a component called MemoryLeakExample. It fetches data from an API using the fetch function in the useEffect hook. The data is stored in the data state variable using the setData function.

The problem arises when the component is unmounted before the data fetching process is completed. If the component is unmounted, the cleanup function defined in the useEffect hook is called. In this case, the cleanup function is responsible for resetting the data state to an empty array.

However, if the component is unmounted before the fetch request completes, the setData function will still be called when the response is received. This can lead to a memory leak because the state update will be attempted on an unmounted component, which can cause issues and potentially retain unnecessary memory.

To fix this memory leak, we need to cancel the ongoing data fetching process if the component is unmounted. One way to achieve this is by using an additional flag variable.

import React, { useState, useEffect } from 'react';
const MemoryLeakExample = () => {
const [data, setData] = useState([]);
const [isMounted, setIsMounted] = useState(true); // Flag to track component's mount status
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();

if (isMounted) { // Check if component is still mounted before updating the state
setData(result);
}
};
fetchData();
return () => {
// Clean up function
setIsMounted(false); // Set the flag to false when component is unmounted
};
}, []);
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};
export default MemoryLeakExample;

In this updated example, we introduced the isMounted flag as a state variable. Initially, it is set to true when the component is mounted. In the fetchData function, we check the isMounted flag before updating the state with the fetched data. If the component is unmounted, the state update will be skipped.

In the cleanup function, we set the isMounted flag to false, indicating that the component is unmounted. This ensures that if the fetch request completes after the component is unmounted, the state update will be skipped.

By using the isMounted flag, we prevent the memory leak by avoiding state updates on an unmounted component, which could cause issues and unnecessary memory retention.

Example-2:

import React, { useState, useEffect } from 'react';
const MemoryLeakExample = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
clearInterval(interval); // Clear the interval when the component is unmounted
};
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default MemoryLeakExample;

In this example, we have a component called MemoryLeakExample that displays a count value and an increment button. The count value is initially set to 0 using the useState hook.

The issue in this code is that we’re starting an interval timer in the useEffect hook without clearing it when the component is unmounted. Whenever the state variable count changes, the effect is triggered, and a new interval is started.

If the user navigates away from the component or unmounts it before the interval is cleared, the interval will continue running in the background, even though the component is no longer visible. This can lead to a memory leak because the interval closure retains a reference to the component and prevents its garbage collection, consuming unnecessary memory.

To fix this memory leak, we need to clear the interval when the component is unmounted. We can achieve this by returning a cleanup function from the useEffect hook.

import React, { useState, useEffect } from 'react';

const MemoryLeakExample = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => {
clearInterval(interval); // Clear the interval when the component is unmounted
};
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
</div>
);
};
export default MemoryLeakExample;

In the updated code, we’ve made two changes. First, we removed the count dependency from the dependency array in the useEffect hook. By doing this, the effect will only run once, when the component is mounted.

Second, inside the interval function, we updated the setCount call to use the functional form, which takes the previous state value as an argument. This ensures that the count state is correctly incremented even if the interval execution is delayed due to other tasks.

With these changes, when the component is unmounted, the cleanup function is called, and the interval is cleared using clearInterval. This prevents the interval from running indefinitely in the background, avoiding the memory leak.

Remember, it’s crucial to clean up any resources (such as intervals, subscriptions, event listeners) when a React component is unmounted to prevent memory leaks and unnecessary resource usage.

3. Event listeners:

If you attach event listeners to elements within your React components, but fail to remove them when the component unmounts, it can lead to memory leaks. Make sure to remove event listeners in the component’s cleanup function, such as in the useEffect hook with a cleanup function or in a class component's componentWillUnmount lifecycle method.

useEffect(() => {
document.addEventListener('click', handleClick);

return () => {
// Cleanup event listner
document.removeEventListener('click', handleClick);
};
}, []);

4. Subscription-based APIs:

If you’re using subscription-based APIs, such as WebSocket connections or observables, it’s crucial to unsubscribe or close these subscriptions when they’re no longer needed. Failing to unsubscribe from these sources can keep references active and cause memory leaks. Use the cleanup function in useEffect or componentWillUnmount to unsubscribe or close these subscriptions.

useEffect(() => {
const subscription = new Subscription();
subscription.subscribe((data) => {
console.log('Received data:', data);
});

return () => {
subscription.unsubscribe();
};
}, []);

5. Improper closure usage

Closures can cause memory leaks if they unintentionally retain references to objects that should be garbage collected. For example, if you use closures inside event handlers and capture variables from the component’s scope, those captured variables can keep the entire component and its associated objects in memory. Be mindful of what variables are captured in closures and consider using the useCallback hook to memoize functions properly.

6. Third-party libraries:

Some third-party libraries may introduce memory leaks if they are not used correctly. Ensure that you follow the documentation and best practices provided by the library maintainers, especially when it comes to cleanup and resource disposal.

--

--