Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A React hook that tells if the component is mounted.
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
}
@zmeyc
Copy link

zmeyc commented Aug 3, 2020

Returning a function will trigger effects on each re-render if you're using it like this:

const isMounted = useIsMounted()
useEffect(() => {
  ...will be triggered on each re-render...
}, [isMounted])

Instead of returning a new instance of function each time, memoize it:

import { useCallback, useEffect, useRef } from 'react'

export const useIsMounted = () => {
  const isMounted = useRef(false);
  useEffect(() => {
    isMounted.current = true;
    return () => {
      isMounted.current = false;
    };
  }, []);
  const checker = useCallback(() => {
    return isMounted.current;
  }, []);
  return checker;
};

const isMounted = useIsMounted();

useEffect(() => {
   fetch(...).then({
     if (isMounted()) { ... }
   })
}, [isMounted])

@geoguide
Copy link

geoguide commented Dec 12, 2020

I'm curious why React wouldn't do this in useState itself?

@jpodpro
Copy link

jpodpro commented Mar 12, 2021

@geoguide really trying to understand this myself.

@kopax-polyconseil
Copy link

kopax-polyconseil commented Jun 15, 2021

Do you guys have the unit test of the short way as well :)?

@Stevemoretz
Copy link

Stevemoretz commented Aug 9, 2021

Use this one instead: https://github.com/jmlweb/isMounted
It doesn't use state for this, which is good.

yarn add ismounted

Thanks the trick was to return the ref itself not ref.current that way it doesn't need state.

@Stevemoretz
Copy link

Stevemoretz commented Aug 9, 2021

I'm curious why React wouldn't do this in useState itself?

Well the warning doesn't mention where it happens, it just says it happened which means they don't know where this problem occurs to solve it, probably useState is not aware at all on which component it is, to see if it was unmounted not run it, just saying I'm not sure though.

@tamvo22
Copy link

tamvo22 commented Nov 17, 2021

I hope I'm not too late to the party. This is working fine for me so far.

import { useCallback, useEffect, useRef } from 'react';

export function useIsMounted(): { current: boolean } {
  const isMounted = useRef(false);
  useEffect(() => {
    isMounted.current = true;

    return () => {
      isMounted.current = false;
    };
  }, []);

  const mounted = useCallback(() => {
    return isMounted;
  }, []);

  return mounted();
}
import { useState, useEffect } from 'react';
import { useIsMounted } from '@/helper/useIsMounted';

export default function TestIsMounted () {
const [state, stateSet] = useState(false);

const isMounted = useIsMounted();

useEffect(() => {
  isMounted.current && stateSet(true);
}, [state])

...

return (...)
}

@daveteu
Copy link

daveteu commented Dec 2, 2021

why don’t return isMount.current? When you use the hook, what you want is just true or false. Does return isMount.current break the hook?

@tamvo22
Copy link

tamvo22 commented Dec 3, 2021

Hi Daveteu,

You are correct. I tried returning isMount.current and got it to work. Thank you.

export default function useIsMounted(): boolean {
  const isMounted = useRef(true);
  useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);

  const mounted = useCallback(() => {
    return isMounted.current;
  }, []);

  return isMounted()
}
const isMounted = useIsMounted();

useEffect(() => {
  isMounted && stateSet(true);
}, [state])

@o-az
Copy link

o-az commented Jan 16, 2022

What's the point of the setState part? I think the isMount hook itself is sufficient.

@Stevemoretz
Copy link

Stevemoretz commented Jan 16, 2022

Think about it, if you don't change the state you will not be able to for instance use useEffect on the result of that hook, basically a rerender won't occur, of course you can reach the value if you want to and it is changed but you can't monitor the changes.

@daveteu
Copy link

daveteu commented Jan 17, 2022

Normally you don't use the result from the hook as a dependency of a useEffect. You only use it for check.

E.g.

useEffect(() => {
  if (isMounted) {
    do something.
 }

},[props])

@jaydenseric
Copy link
Author

jaydenseric commented Jan 17, 2022

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

@Stevemoretz
Copy link

Stevemoretz commented Jan 17, 2022

@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.

@zmeyc
Copy link

zmeyc commented Apr 3, 2022

@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

@pharmpy-dev-123
Copy link

pharmpy-dev-123 commented Oct 17, 2022

@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 the useEffect 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.

@tamvo22
Copy link

tamvo22 commented Oct 18, 2022

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment