-
-
Save AlpacaGoesCrazy/25e3a15fcd4e57fb8ccd408d488554d7 to your computer and use it in GitHub Desktop.
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 |
@AlpacaGoesCrazy just a thought-wouldn't it be better to solve this one level higher?
So use https://github.com/mauricedb/use-abortable-fetch instead of like a regularuseFetch
and you should have it covered without hacking around theuseState
. Right?
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)
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];
};
@AlpacaGoesCrazy just a thought-wouldn't it be better to solve this one level higher?
So use https://github.com/mauricedb/use-abortable-fetch instead of like a regular
useFetch
and you should have it covered without hacking around theuseState
. Right?