Borislav Hadzhiev
Thu Apr 28 2022·2 min read
Photo by Priscilla Du Preez
To solve the "Warning: Can't perform a React state update on an unmounted
component", declare an isMounted
boolean in your useEffect
hook that is used
to track whether the component is mounted. A component's state should only be
updated if the component is mounted.
import {useState, useEffect} from 'react'; const App = () => { const [state, setState] = useState(''); useEffect(() => { // 👇️ set isMounted to true let isMounted = true; async function fetchData() { const result = await Promise.resolve(['hello', 'world']); // 👇️ only update state if component is mounted if (isMounted) { setState(result); } } fetchData(); return () => { // 👇️ when component unmounts, set isMounted to false isMounted = false; }; }, []); return ( <div> <h2>State: {JSON.stringify(state)}</h2> </div> ); }; export default App;
A straight forward way to get rid of the warning is to keep track of whether the
component is mounted using an isMounted
boolean in our
useEffect hook.
We initialized the isMounted
boolean to true
in useEffect
.
Our fetchData
function performs some async task, most commonly an API request
and updates the state based on the response.
However, note that we only update the state if the isMounted
variable is set
to true
.
async function fetchData() { const result = await Promise.resolve(['hello', 'world']); // 👇️ only update state if component is mounted if (isMounted) { setState(result); } }
This helps us avoid the warning because we aren't updating the state if the component isn't mounted.
The function we returned from the useEffect
hook is called when the component
unmounts.
return () => { // 👇️ when component unmounts, set isMounted to false isMounted = false; };
We set the isMounted
variable to false
to indicate that the component is no
longer mounted.
If our fetchData
function is invoked after the component unmounts, the if
block won't run because isMounted
is set to false
.
async function fetchData() { const result = await Promise.resolve(['hello', 'world']); // 👇️ only update state if component is mounted if (isMounted) { setState(result); } }
If you do this often, you can extract the logic into a reusable hook.
import {useState, useEffect, useRef} from 'react'; // 👇️ extract logic into reusable hook function useIsMounted() { const isMounted = useRef(false); useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; }; }); return isMounted; } const App = () => { const [state, setState] = useState(''); // 👇️ use hook const isMountedRef = useIsMounted(); useEffect(() => { async function fetchData() { const result = await Promise.resolve(['hello', 'world']); // 👇️ only update state if component is mounted if (isMountedRef.current) { setState(result); } } fetchData(); }, [isMountedRef]); return ( <div> <h2>State: {JSON.stringify(state)}</h2> </div> ); }; export default App;
The useRef() hook can be
passed an initial value as an argument. The hook returns a mutable ref object
whose .current
property is initialized to the passed argument.
We track whether the component is mounted in our useIsMounted
hook, just like
we did directly in the component's useEffect
hook.
Notice that in our fetchData
function, we have to check for the value of
isMountedRef.current
because the current
property on the ref is the ref's
actual value.