Last active
December 7, 2020 17:49
-
-
Save AlpacaGoesCrazy/25e3a15fcd4e57fb8ccd408d488554d7 to your computer and use it in GitHub Desktop.
Hook for react state which prevents updates on unmounted components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useRef, useState } from 'react' | |
/* | |
If you attempt to set some state after asynchronous request it may happen that component you wish to set state on has been unmounted. | |
This will trigger "Warning: Can’t call setState (or forceUpdate) on an unmounted component." warning. | |
This hooks is `useState` hook which prevents setting state on an unmounted component | |
Usage: | |
const [myState, mySafeSetState] = useSafeState(initialValue) | |
*/ | |
const useSafeState = (initialValue) => { | |
const _isMounted = useRef() // useRef to memorize if the component is mounted between renders | |
const [state, setState] = useState(initialValue) | |
useEffect(() => { | |
_isMounted.current = true | |
return () => { | |
_isMounted.current = false | |
} | |
}) | |
const safeSetState = (...args) => { | |
if(_isMounted.current) { // do not call setState if the component already unmounted | |
setState(...args) | |
} | |
} | |
return [state, safeSetState] | |
} | |
export default useSafeState |
Thanks for creating this @AlpacaGoesCrazy! Super useful. I've modified it a bit to work in TypeScript version – type-compatible drop-in replacement for useState
:
https://gist.github.com/troygoode/0702ebabcf3875793feffe9b65da651a
I have found that this does not preserve setState
function identity (in contrast to React), which led to suprising bugs when replacing useState
with useSafeState
. I have therefore modified the implementation to:
export const useSafeState = (initialValue) => {
const _isMounted = useRef();
const [state, setState] = useState(initialValue);
const _setState = useRef((...args) => {
if (_isMounted.current) { setState(...args); }
});
useEffect(() => {
_isMounted.current = true;
return () => { _isMounted.current = false; };
});
return [state, _setState.current];
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It is not always the case that your async function is invoked in the same component where it was declared, and not all your async functions are API calls. And if you do decide to use that library you will need to track component unmounting and manually call
abort
.On top of that you might not want to abort your async request. If you start the request and navigate away you still might want to finish it and put it in cache so there is no need to invoke it on navigation back. The point is to not update state of the unmounted component when you finish your request like
setIsLoading(false)