How to resolve Warning: Can’t perform a React state update on an unmounted component

Acting as a front-end developer can be challenging and rewarding, but also frustrating at times. One of the common issues that you may encounter is getting a warning like this:

Warning: 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.

What does this warning mean and how can you fix it? In this blog post, I will explain the cause and the solution of this problem, and provide some sources for further reading.

The cause of the warning is that you are trying to update the state of a component that has been unmounted from the DOM. This can happen if you have an asynchronous operation, such as fetching data from an API, that resolves after the component is unmounted. For example, imagine you have a component that fetches some data when it mounts, and displays it in a table:

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

function DataComponent() {
const [data, setData] = useState([]);

useEffect(() => {
axios.get('/api/data')
.then(response => {
setData(response.data);
})
.catch(error => {
console.error(error);
});
}, []);

return (
<div>
<h1>Data</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.age}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

export default DataComponent;

Now, suppose you have another component that renders the DataComponent conditionally based on some state:

import React, { useState } from 'react';
import DataComponent from './DataComponent';

function App() {
const [showData, setShowData] = useState(true);

return (
<div>
<button onClick={() => setShowData(!showData)}>
Toggle Data
</button>
{showData && <DataComponent />}
</div>
);
}

export default App;

If you run this app and click the toggle button before the data is fetched, you will see the warning in the console. This is because the DataComponent is unmounted before the axios.get promise is resolved, and then tries to call setData with the response data. This is a no-op, meaning it does not affect the UI, but it indicates a memory leak because the component is still holding a reference to the state setter function.

The solution to this problem is to cancel the asynchronous operation in a cleanup function that is returned from the useEffect hook. This way, when the component is unmounted, the promise is canceled and does not try to update the state. To cancel a promise, you can use an axios cancel token, which is an object that can be used to cancel a request. Here is how you can modify the DataComponent to use a cancel token:

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

function DataComponent() {
const [data, setData] = useState([]);

useEffect(() => {
// create a cancel token
const source = axios.CancelToken.source();

axios.get('/api/data', {
// pass the cancel token as an option
cancelToken: source.token
})
.then(response => {
setData(response.data);
})
.catch(error => {
// check if the error is caused by cancellation
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
console.error(error);
}
});

// return a cleanup function that cancels the request
return () => {
source.cancel('Operation canceled by the user.');
};
}, []);

return (
<div>
<h1>Data</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.age}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

export default DataComponent;

Now, if you run the app and click the toggle button before the data is fetched, you will not see the warning anymore. Instead, you will see a message in the console saying that the request was canceled. This means that the component is no longer trying to update the state after it is unmounted, and there is no memory leak.

This is one way to fix the warning, but there may be other ways depending on your specific situation. The main idea is to make sure that you cancel or abort any asynchronous operations that may resolve after the component is unmounted, and that you use a cleanup function to do so.

If you want to learn more about this topic, here are some sources that I recommend:

Axios documentation on canceling requests

Leave a Reply

Your email address will not be published. Required fields are marked *