Can't perform a React state update on an unmounted component
Here is a React Hooks specific solution for
Error
Warning: Can't perform a React state update on an unmounted component.
Solution
You can declare let isMounted = true
inside useEffect
, which will be changed in the cleanup callback, as soon as the component is unmounted. Before state updates, you now check this variable conditionally:
useEffect(() => {
let isMounted = true; // note this flag denote mount status
someAsyncOperation().then(data => {
if (isMounted) setState(data);
})
return () => { isMounted = false }; // use effect cleanup to set flag false, if unmounted
});
const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};
const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useEffect(() => {
let isMounted = true; // note this mounted flag
fetchData();
return () => {
isMounted = false;
}; // use effect cleanup to set flag false, if unmounted
// simulate some Web API fetching
function fetchData() {
setTimeout(() => {
// drop "if (isMounted)" to trigger error again
if (isMounted) setState("data fetched");
}, 4000);
}
}, []);
return <div>Child: {state}</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Extension: Custom useAsync
Hook
We can encapsulate all the boilerplate into a custom Hook, that just knows, how to deal with and automatically abort async functions in case the component unmounts before:
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isMounted = true;
asyncFn().then(data => {
if (isMounted) onSuccess(data);
});
return () => { isMounted = false };
}, [asyncFn, onSuccess]);
}
// use async operation with automatic abortion on unmount
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isMounted = true;
asyncFn().then(data => {
if (isMounted) onSuccess(data);
});
return () => {
isMounted = false;
};
}, [asyncFn, onSuccess]);
}
const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useAsync(delay, setState);
return <div>Child: {state}</div>;
};
const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};
const delay = () => new Promise(resolve => setTimeout(() => resolve("data fetched"), 4000));
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
To remove - Can't perform a React state update on an unmounted component warning, use componentDidMount method under a condition and make false that condition on componentWillUnmount method. For example : -
class Home extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
this._isMounted = true;
ajaxVar
.get('https://domain')
.then(result => {
if (this._isMounted) {
this.setState({
news: result.data.hits,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}
If above solutions dont work, try this and it works for me:
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}