BlogReactJS

Creating an isMounted hook with useEffect (ReactJS)

Written by Codemzy on April 13th, 2022

Although isMounted is deprecated in ReactJS, maybe you need it to prevent memory leak errors in your application. Learn why there are better ways to solve this problem, and how to create an isMounted hook with useEffect if you still need one.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.

It’s pretty common to get the above error in your code if you try to set a state when a component is no longer mounted. And while it’s best to write your components to avoid this situation, sometimes it’s unavoidable.

Before we look at how you can replicate the deprecated isMounted function with hooks, let me start by saying, you shouldn’t need to!

isMounted was deprecated for a reason. It hides the error but doesn’t solve the problem. Your redundant code still runs, you just prevent it from setting the state.

If the response triggers a state change then you're going to get that warning message.

A better option is to clean up your code by returning a cleanup function from useEffect. I covered this in more detail in how to use React useEffect like componentDidMount.

But there may be some cases where you can't clean up your code immediately, for example, maybe a debounce function might still run once after a dismount.

Ok, so if you understand why isMounted was depreciated, but still need it, how can you do that with hooks?

Starting with useEffect

As I already covered in how to use React useEffect like componentDidMount, useEffect is the ReactJS hooks way of handling lifecycle changes in your components.

So when you need to know if a component is mounted or not, useEffect is a good place to start.

useEffect(() => {
  // component is mounted
  return () => {
    // component is being unmounted
  }
});

But the above code will run on every render of your component, e.g. when state or props change. That's not what we want here. You just need to useEffect to run on the initial mount, and the finale unmount.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

- ReactJS Using the Effect Hook

So we can add an empty argument to tell useEffect to run once.

useEffect(() => {
  // component is mounted
  return () => {
    // component is being unmounted
  }
}, []); // run once on mount

Adding useRef to store isMounted

Ok, now we need to set some kind of persistent value that will stick around between renders so we know that our component is mounted.

Usually, when you want to keep a value around in a React component, you would reach for state and the useState hook. But we will avoid using state for this because changing the state will trigger a re-render.

A re-render might trigger other code that sets state and we can't set state when a component unmounts, that's kinda the whole problem.

Instead, you can useRef. Because it doesn't do any of that!

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render.

- ReactJS useRef

You'll useEffect to monitor when the component mounts or unmounts, and save that info in a ref.

const isMounted = useRef(false); // unmounted by default

useEffect(() => {
  isMounted.current = true; // mounted
  return () => {
    isMounted.current = false; // unmounted
  }
}, []); // run once on mount

Now anytime you need to know if our component is mounted, you can check isMounted.current to get the current value.

// in an if block
if (isMounted.current) {
  // can set state because component is mounted
}

// shorthand
isMounted.current && setState("Yeah we can do this!");

Adding useCallback to create a custom hook

Let's extract this code into a custom hook we can reuse in any component.

If you want to create a custom hook, you can return the ref, and check the current value like in the code example above.

But there are a couple of improvements you can make.

First of all, we don't want to add .current every time. It doesn't look pretty, and we shouldn't need to remember the hook returns a ref to use it.

You might be tempted to just return isMounted.current from a custom hook (so it just returns true or false. But you would need to pass it as a dependency in any other useEffect you used it in or the value wouldn't update.

Instead, you can simplify things by returning a function that checks isMounted.current.

Make sure to wrap it in a useCallback function though. This will memoize the function so that its value (the function not the ref) doesn't change, and it will avoid re-renders or needing to be a dependency.

Here's how the hook will look.

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

function useIsMounted() {
  const isMounted = useRef(false); // unmounted by default
  const isMountedFunction = useCallback(() => isMounted.current, []);

  useEffect(() => {
    isMounted.current = true; // mounted

    return () => {
      isMounted.current = false; // unmounted
    }
  }, []); // run once on mount

  return isMountedFunction;
};

Or you can refactor that slightly to return the useCallback function like this:

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

function useIsMounted() {
 const isMounted = useRef(false); // unmounted by default

 useEffect(() => {
  isMounted.current = true; // mounted

  return () => {
   isMounted.current = false; // unmounted
  }
 }, []); // run once on mount

 return useCallback(() => isMounted.current, []); // return function that checks mounted status
};

export default useIsMounted;

Using the isMounted custom hook

Now you can use the hook in any component, to check if the component is mounted or not before doing things like setting state.

import React from 'react';
import useIsMounted from './hooks/useIsMounted';

function MyComponent() {
  const [state, setState] = useState('');
  const isMounted = useIsMounted();

  // in an if block
  if (isMounted()) {
    // can set state because component is mounted
  }

  // shorthand
  isMounted() && setState("Yeah we can do this!");

  //...
};