-
-
Save jaydenseric/a67cfb1b809b1b789daa17dfe6f83daa to your computer and use it in GitHub Desktop.
import React from 'react' | |
export const useIsMounted = () => { | |
const ref = React.useRef(false) | |
const [, setIsMounted] = React.useState(false) | |
React.useEffect(() => { | |
ref.current = true | |
setIsMounted(true) | |
return () => (ref.current = false) | |
}, []) | |
return () => ref.current | |
} |
Just be aware that what you want from a useIsMounted
hook is not for it to return a boolean, but to return a way to check on demand the mounted status. For example, maybe you awaited a loading promise and want to update some state, but want to first check the component is still mounted before doing so to avoid a React set state on unmounted component error. If you read a simple boolean that was created at the same render that started the loading side effect, it will be stale. The component may be unmounted because that boolean is out of date.
I don't actually have a need for a useIsMounted
hook anymore, because I use a useForceUpdate
pattern instead, for example: https://github.com/jaydenseric/graphql-react/blob/f7d3a17edb99dc3ffabe8598151df8bf942e2258/useLoadingEntry.mjs#L22
@daveteu You've got a point there, I don't remember why but I needed it in cases like that dependency, it should work without setState for you though.
@tamvo22 @daveteu It's not correct to return isMounted()
from hook because it will reflect the mounted state at the time of hook call, not when async function finishes. I.e. the hook needs to return a 'checker function', not result of the function.
But the function needs to be memoized, otherwise when it's added to useEffect dependencies (eslint and it's autofix will suggest doing this), it will trigger effect recalculation on each re-render. So I think this implementation is correct: https://gist.github.com/jaydenseric/a67cfb1b809b1b789daa17dfe6f83daa?permalink_comment_id=3401782#gistcomment-3401782
@timoxley I have since found ways to avoid having to use a
useIsMounted
hook, but from my rusty memory, the state is updated arbitrarily in theuseEffect
callback to trigger a re-render? I worked through a lot of gotchas to get it going.
The state update is in fact an instance of the forceUpdate
pattern.
@zmeyc Thank you for the clarification! I tested with a delayed API calls and found that you are right. As you pointed out, it only reflected the initial mount status hook call. All this time, I was under the assumption that isMounted would return the useRef value at the time of the API request. When I pass the function isMounted() to call during the API return, it does provide the correct mounted value when my test component mounted and unmounted. Thanks again cheers!
One more option to the initial value being true or false debate:
const useIsMounted = (initialValue = true) => {
const ref = useRef(initialValue)
useEffect(() => {
ref.current = true
return () => {
ref.current = false
}
}, [])
return ref
}
Let that value be passed in. If true
then it behaves like its mounted initially and only changes to false
when it's unmounted. Useful to stop yourself from setting state when an async action is done after a component was unmounted. But if false
then it's treated like the original solution where it still flips to false
when unmounted but also flips to true
when ready in the client.
Normally you don't use the result from the hook as a dependency of a useEffect. You only use it for check.
E.g.