Brad Westfall
Posted on March 23, 2023
(Originally posted at ReactTraining.com)
There's a variety of tricks that you see out there for determining if the component is mounted. These are usually code-smell because you're thinking in terms of "time". We thought in terms of time with class components but we don't want to with functional components and Hooks.
Instead you should think in terms of snapshots with functional components. In other words, based on this state here is a snapshot of my UI without regards to how long the component has lived on the screen.
Snapshots are "moments" in time sure, but we're not thinking in terms of "this is the early life of my component" or "late life". We're not thinking in terms of the component being mounted still.
When I see this custom Hook, I usually see bugs soon after:
const isMounted = useIsMounted()
I guess the motivation is to reduce this kind of boilerplate:
useEffect(() => {
let isCurrent = true
getUser(userId).then((user) => {
if (isCurrent) {
setUser(user)
}
})
return () => (isCurrent = false)
}, [userId])
I suppose the intention is to do something like this now instead of the approach above?
const isMounted = useIsMounted()
useEffect(() => {
getUser(userId).then((user) => {
if (isMounted) {
setUser(user)
}
})
}, [userId])
At first it seems like we're trying to prevent ourselves from setting state on an unmounted component? But did you know that first code with the cleanup fixes other bugs and the second one without the cleanup has bugs?
Also, avoiding setting state on an unmounted component is not something you need to be concerned with.
In a pull request where the React team discusses removing the warning for setting state on an unmounted component, Dan Abramov discusses this an isMountedRef
trick as being one of the canonical approaches to certain problems and says it's a "pretty clumsy" solution that will give you false positives in React goes in a certain future direction.
In the future, we'd like to offer a feature that lets you preserve DOM and state even when the component is not visible, but disconnect its effects. With this feature, the code above doesn't work well. You'll "miss" the setPending(false) call because isMountedRef.current is false while the component is in a hidden tab
To be clear, I'm talking about some component-wide general use variable like isMounted
.
Doing proper cleanups like this is not what I'm referring to:
useEffect(() => {
let isMounted = true
getUser(userId).then((user) => {
if (isMounted) {
setUser(user)
}
})
return () => (isMounted = false)
}, [userId])
The above code is good because we're properly cleaning up this effect. I don't like the variable name isMounted
because the developer who writes it is probably confused about when the cleanup gets called, but the variable name doesn't matter in terms of how this code fixes a variety of issues.
These are the anti-pattern "component-wide" variables that either create bugs (because they encourage you to not use specific cleanups) or just add extra unneeded logic like preventing us from setting state on an unmounted component in situations that are not actually problematic to do so:
const isMounted = useIsMounted()
useEffect(() => {
getUser(userId).then((user) => {
if (isMounted) {
setUser(user)
}
})
}, [userId])
function someEvent() {
if (isMounted) {
// do stuff
}
}
As I'm writing this I did a quick search on Google to see if there were similar articles and this was the first thing that came up from the old React docs π
https://legacy.reactjs.org/blog/2015/12/16/ismounted-antipattern.html
This was from the era of class-based components, but still it's always been an anti-pattern folks!
Thanks for reading.
Posted on March 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.