Is there a way to check if the react component is unmounted?
Asked Answered
C

14

108

I have a usecase where i need to unmount my react component. But in some cases, the particular react component is unmounted by a different function. Hence, I need to check if the component is mounted before unmounting it.

Chevalier answered 29/9, 2016 at 10:13 Comment(1)
nextjs.org/docs/messages/react-hydration-errorVentriloquism
B
143

Since isMounted() is being officially deprecated, you can do this in your component:

componentDidMount() { 
  this._ismounted = true;
}

componentWillUnmount() {
   this._ismounted = false;
}

This pattern of maintaining your own state variable is detailed in the ReactJS documentation: isMounted is an Antipattern.

Bradford answered 29/9, 2016 at 10:36 Comment(5)
Thanks for the tip ... from the docs ... Just set a _isMounted property to true in componentDidMount and set it to false in componentWillUnmount ... however, using this.setState({mounted: false}); may trigger too late to prevent the warning since state changes do not happen immediately - better to just use a class property for this ... this.mounted = false - thanks tho, this pointed me in the right directionLouannlouanna
@danday74, yeah you are correct. I probably missed the point when I wrote the answer. Consider upvoting the answer if it helpedBradford
@KevinGhaboosi, Unfortunately only the person who asked the question would be able to accept this as an answerBradford
"Read the anti-pattern link more carefully. Using the method isMounted() is an anti-pattern, but using a property, like _isMounted is recommended." #49906937Scabies
If the component had already unmounted then won't this._ismounted be undefined?Daberath
A
56

I'll be recommended you to use the useRef hook for keeping track of component is mounted or not because whenever you update the state then react will re-render the whole component and also it will trigger the execution of useEffect or other hooks.

function MyComponent(props: Props) {
  const isMounted = useRef(false)

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

  return (...);
}

export default MyComponent;

and you check if the component is mounted with if (isMounted.current) ...

Affliction answered 3/9, 2020 at 8:26 Comment(5)
Use https://mcmap.net/q/202560/-is-there-a-way-to-check-if-the-react-component-is-unmounted for class components. Use this for hooks.Seafarer
Did you mean to say you do recommend use of useState instead of "don't"? Because your solution seems to use itApodaca
The cleanup function will be called if you have multiple useEffects in the single component, so this does not work, as it would not differ between re-renders and re-mounts.Sibley
"Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect." reactjs.org/docs/hooks-reference.html#cleaning-up-an-effectSibley
The effect isn't re-executed after mount, since its dependency array is empty.Glycerinate
C
26

I think that Shubham answer is a workaround suggested by react for people that need to transition their code to stop using the isMounted anti-pattern.

This is not necessarily bad, but It's worth listing the real solutions to this problem.

The article linked by Shubham offers 2 suggestions to avoid this anti pattern. The one you need depends on why you are calling setState when the component is unmounted.

if you are using a Flux store in your component, you must unsubscribe in componentWillUnmount

class MyComponent extends React.Component {
  componentDidMount() {
    mydatastore.subscribe(this);
  }
  render() {
    ...
  }
  componentWillUnmount() {
    mydatastore.unsubscribe(this);
  }
}

If you use ES6 promises, you may need to wrap your promise in order to make it cancelable.

const cancelablePromise = makeCancelable(
  new Promise(r => component.setState({...}}))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
  .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise

Read more about makeCancelable in the linked article.

In conclusion, do not try to patch this issue by setting variables and checking if the component is mounted, go to the root of the problem. Please comment with other common cases if you can come up with any.

Carnelian answered 29/8, 2017 at 14:24 Comment(3)
Imagine a list of posts. Each post is a separate component and has a delete button next (inside) to it. When user clicks the button, post sets isDeleting = true (to disable the button), tells the parent component to delete the post (using callback passed via props), and when he's done (another callback, but to the post component) it might need to enable the button. E.g. when HTTP error occurs (post has not been deleted). That is, the post component might need to change state depending on whether it's still mounted or not. Is there anything wrong with that?Pegg
Just to point out for anyone still coming here. The makeCancelable method in the article referenced to is setting a local variable - just like Shubham's example. There is no way of actually cancelling a Promise. I have a hard time to see why setting a boolean in some other place would be better that having it clearly in the React component. The point about unsubscribing subscriptions is valid though.Carrasquillo
My component sets a proxy on an object's property. When that object updates, the proxy calls setState so that the property and the component's state are in sync. So in this case the suggestions are irrelevant.Solvent
B
23

Another solution would be using Refs . If you are using React 16.3+, make a ref to your top level item in the render function.

Then simply check if ref.current is null or not.

Example:

class MyClass extends React.Component {
  constructor(props) {
    super(props);
    this.elementRef = React.createRef();
  }

  checkIfMounted() {
     return this.elementRef.current != null;
  }

  render() {
    return (
      <div ref={this.elementRef} />
    );
  }
}
Butyraceous answered 7/8, 2018 at 4:33 Comment(0)
A
15

Using @DerekSoike answer, however in my case using useState to control the mounted state didn't work since the state resurrected when it didn't have to

What worked for me was using a single variable

myFunct was called in a setTimeout, and my guess is that when the same component initialized the hook in another page it resurrected the state causing the memory leak to appear again

So this didn't work for me

  const [isMounted, setIsMounted] = useState(false)

  useEffect(() => {
    setIsMounted(true)
    return () => setIsMounted(false)
  }, [])

  const myFunct = () => {
    console.log(isMounted) // not always false
    if (!isMounted) return
    // change a state
  }

And this did work for me

  let stillMounted = { value: false }
  useEffect(() => {
    stillMounted.value = true
    return () => (stillMounted.value = false)
  }, [])

  const myFunct = () => {
    if (!stillMounted.value) return
    // change a state
  }
Aleksandropol answered 11/12, 2019 at 10:12 Comment(2)
This worked while the previous strategy (which is updated at many places didn't)... Thanks @AleksandropolMorril
it will reload the functional component is we update isMounted state to true or false instead I'll recommended use the let variable.Affliction
U
4

I got here because I was looking for a way to stop polling the API.

The react docs does cover the websocket case, but not the polling one.

The way I worked around it

// React component

React.createClass({
    poll () {
        if (this.unmounted) {
            return
        }
        // otherwise, call the api
    }
    componentWillUnmount () {
        this.unmounted = true
    }
})

it works. Hope it helps

Please, let me know if you guys know any failing test case for this =]

Uhlan answered 26/3, 2017 at 3:41 Comment(2)
it works, but what if you have multiple instances? all will take a reference to the same allowPollingWastebasket
Yeah, you'd better but it in the instance, I'll edit the code snippet.Uhlan
M
4

If you're using hooks:

function MyComponent(props: Props) {
  const [isMounted, setIsMounted] = useState<boolean>(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  useEffect(() => {
    return () => {
      setIsMounted(false);
    }
  }, []);

  return (...);
}

export default MyComponent;
Millimeter answered 9/12, 2019 at 23:2 Comment(3)
This post dev.to/trentyang/replace-lifecycle-with-hooks-in-react-3d4n would help to give more details of how to convert from React Native Class style to React Hooks styleDominations
@ZiaUlRehmanMughal The first useEffect hook is called once on component mount since an empty array is passed as the dependencies list. The second useEffect hook is passed a clean up function that gets called on unmount. Take a look at the docs for more detail: reactjs.org/docs/hooks-reference.html#useeffect -Millimeter
This is a memory leak. You are calling setState on a component that has been unmounted. I suggest people to use this other answerKramer
H
1

The same idea but enother implementation

/**
 * component with async action within
 * 
 * @public
 */
class MyComponent extends Component {
    constructor ( ...args ) {
        // do not forget about super =)
        super(...args);
        // NOTE correct store "setState"
        let originSetState = this.setState.bind(this);
        // NOTE override
        this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
    }
    /**
     * no necessary setup flag on component mount
     * @public
     */
    componentWillUnmount() {
        // NOTE setup flag
        this.isUnmounted = true;
    }
    /**
     *
     * @public
     */
    myCustomAsyncAction () {
        // ... code
        this.setState({any: 'data'}); // do not care about component status
        // ... code
    }

    render () { /* ... */ }
}
Hubie answered 3/7, 2018 at 11:55 Comment(0)
M
0

I have solve with hot reload and react to different it events ✅

   const {checkIsMounted} = useIsMounted(); //hook from above
   useEffect(() => {
    //here run code
    return () => {
      //hot reload fix
      setTimeout(() => {
        if (!checkIsMounted()) {
          //here we do unmount action 
        }
      }, 100);
    };
  }, []); 
Mauceri answered 5/4, 2022 at 9:22 Comment(0)
F
0

Pproblem

There is a problem when using the useState() hook. If you are also trying to do something else in a useEffect function (like fetching some data when the component is mounted) at the same time with setting the new value for the hook,

const [isMounted, setIsMounted] = useState(false)

useEffect(() =>
{
    setIsMounted(true) //should be true

    const value = await fetch(...)

    if (isMounted) //false still
    {
        setValue(value)
    }

    return () => 
    {
        setIsMounted(false)
    }
}, [])

the value of the hook will remain the same as the initial value (false), even if you have changed it in the beggining. It will remain unchanged for that first render, a new re-render being required for the new value to be applied. For some reason @GWorking solution did not work too. The gap appears to happen while fetching, so when data arrives the component is already unmounted.

Solution

You can just combine both and and check if the component is unmounted during any re-render and just use a separate variable that will keep track to see if the component is still mounted during that render time period

const [isMounted, setIsMounted] = useState(false)
let stillMounted = { value: false }

useEffect(() =>
{
    setIsMounted(true)
    stillMounted.value = true

    const value = await fetch(...)

    if (isMounted || stillMounted.value) //isMounted or stillMounted
    {
        setValue(value)
    }

    return () => 
    {
        (stillMounted.value = false)
    
        setIsMounted(false)
    }
}, [isMounted]) //you need to also include Mounted values

Hope that helps someone!

Fabricate answered 16/6, 2022 at 18:38 Comment(0)
E
0

There's a simple way to avoid 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. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

You can redefine setState method inside your class component using this pattern:

    componentWillUnmount() {
        this._unmounted = true;
    }

    setState(params, callback) {
        this._unmounted || super.setState(params, callback);
    }

Efflux answered 29/7, 2022 at 10:54 Comment(0)
B
0

This may be irrelevant to the OP's question, but I came here from google and I didn't find my answer here, so I wrote one:

import {useEffect, useRef} from 'react';

export function useUnmounted(effect) {
  const callbackRef = useRef(effect);

  useEffect(() => {
    callbackRef.current = effect;
  }, [effect]);

  useEffect(() => {
    return () => {
      callbackRef.current();
    };
  }, []);
}

usage:

// somewhere in your component:
  useUnmounted(() => {
    // do something before getting unmounted
  });
Bombardier answered 23/7, 2023 at 12:39 Comment(0)
I
-1

i found that the component will be unmounted, generate fill this var

if(!this._calledComponentWillUnmount)this.setState({vars});
Indefinable answered 6/3, 2017 at 8:46 Comment(3)
this is an internal property, I don't think it's safe to play with them because it might change in the future.Wastebasket
Well, to be honest, it is safe until you update the library, so in some cases, it is a good solution. But there should be a condition !== undefined to be sure that property is there.Issuant
But yes, it is better to go with official advice, of course.Issuant
A
-1

You can use:

myComponent.updater.isMounted(myComponent)

"myComponent" is instance of your react component. this will return 'true' if component is mounted and 'false' if its not..

  • This is not supported way to do it. you better unsubscribe any async/events on componentWillUnmount.
Attenuation answered 25/10, 2018 at 22:39 Comment(3)
Thanks, this helped in debugging I was doing.Tatiania
for some reason, this solution does not work in prod :/Alpaca
@NourSIDAOUI This feature is officially deprecated since 2016. reactjs.org/blog/2015/12/16/ismounted-antipattern.htmlImitable

© 2022 - 2024 — McMap. All rights reserved.