How to Check If the React Component Is Unmounted

How can I check if the component is unmounted in a functional component?

Here is some pseudo code how you can use useEffect to see if a component is mounted.

It uses useEffect to listen to someService when it receives a message it checks if the component is mounted (cleanup function is also called when component unmounts) and if it is it uses setServiceMessage that was created by useState to set messages received by the service:

import { useState, useEffect } from 'react';
import someService from 'some-service';

export default props => {
const userId = props.userId;
const [serviceMessage, setServiceMessage] = useState([]);
useEffect(
() => {
const mounted = { current: true };
someService.listen(
//listen to messages for this user
userId,
//callback when message is received
message => {
//only set message when component is mounted
if (mounted.current) {
setServiceMessage(serviceMessage.concat(message));
}
});
//returning cleanup function
return () => {
//do not listen to the service anymore
someService.stopListen(userId);
//set mounted to false if userId changed then mounted
// will immediately be set to true again and someService
// will listen to another user's messages but if the
// component is unmounted then mounted.current will
// continue to be false
mounted.current = false;
};
},//<-- the function passed to useEffects
//the function passed to useEffect will be called
//every time props.userId changes, you can pass multiple
//values here like [userId,otherValue] and then the function
//will be called whenever one of the values changes
//note that when this is an object then {hi:1} is not {hi:1}
//referential equality is checked so create this with memoization
//if this is an object created by mapStateToProps or a function
[userId]
);
};

How to determine if a component has been remounted?

The solution I used to determine which component was being unmounted was to add a call to a debugging utility in each component. This is actual example output that very easily showed the culprit:

TopLevelRoutes: #useEffect cleanup
debugging-utils.ts:20 ClientsTopRoute: rendering (total: #74
debugging-utils.ts:5 ClientsTopRoute, seconds since last mount: 118
debugging-utils.ts:20 Clients/Listings: rendering (total: #371
debugging-utils.ts:5 Clients/Listings, seconds since last mount: 0
Clients/Listing: totalRenders 371
Listings.tsx:230 Clients/Listings: viewClientDrawer false
debugging-utils.ts:20 Clients/Listings: rendering (total: #372
debugging-utils.ts:5 Clients/Listings, seconds since last mount: 0
Listings.tsx:99 Clients/Listing: totalRenders 372
Listings.tsx:230 Clients/Listings: viewClientDrawer false
debugging-utils.ts:10 Clients/Listings: #useEffect cleanup
debugging-utils.ts:10 ClientsTopRoute: #useEffect cleanup
index.tsx:34 TopLevelRoutes count: 120
debugging-utils.ts:20 ClientsTopRoute: rendering (total: #75
debugging-utils.ts:5 ClientsTopRoute, seconds since last mount: 118

You can easily spot from this that the Clients/Listings components is being unmounted and mounted on every re-render of its parent ClientsTopRoute.

I got this by adding this to each suspected component:

import { createRenderingInfoPrinter } from "../debugging-utils";

const debug = createRenderingInfoPrinter("ClientsTopRoute");

const ClientsTopRoute: React.FC = () => {
debug();
return <div> ...</div>

The debug hook I created looks like this:

/* debugging-utils.ts */

import React from "react";

export function usePrintSecondsSinceLastMount(identifier: string) {
const [seconds, setSeconds] = React.useState(0);
console.debug(`${identifier}, seconds since last mount: ${seconds}`);

React.useEffect(() => {
const timer = setInterval(() => setSeconds(seconds + 1), 1000);
return () => {
console.debug(`${identifier}: #useEffect cleanup`);
clearInterval(timer);
};
}, [seconds]);
}

export function createRenderingInfoPrinter(identifier: string) {
let count = 0;
return () => {
count++;
console.debug(`${identifier}: rendering (total: #${count}`);
usePrintSecondsSinceLastMount(identifier);
};
}

If you do not care about the total number of renders you can of course just use the usePrintSecondsSinceLastMount hook directly.


For completeness, my situation of unmounting was due to a inconspicuous looking bit of React Router code left by my predecessors:




The key here is that the HOC components created by withRouter are re-created on every re-render. Therefore, they do not share anything with the subtree of the previous VDOM and so an unmount of the existing node takes place. Simply extracting the HOC out of the render function, as const Listing = withRouter(ClientsListingComponent), fixed the issue.

This situation is similar to the only relevant thread I could find on the matter where a JSX functional component was created in the render phase.

How to check if react component detached

React provides various methods to keep track of a component's lifecycle. And in your case you need to track if a component has unmounted. So, there are 2 approach for this based on the type of component you are using:

Class Component

Use componentUnmount lifecycle method.

class YourComponent extends Component {
constructor(props) {
super(props);
}

componentWillUnmount() {
// this method is invoked immediately before a component
// is unmounted and destroyed. you can perform any necessary
// cleanup in this method, such as invalidating
// timers, canceling network requests,
// or cleaning up subscriptions
}

render() {
return (
<div>
{/* ...contents... */}
</div>
);
}
}

Functional Component

Leverage useEffect hook with cleanup.

const YourComponent = () => {
useEffect(() => {
// rest of code

return () => {
// similar to componentWillUnmount() method, this function
// would invoke at the time of component's unmount.
};
},[]);

return (
<div>
{/* ...contents... */}
</div>
);
}

reactjs: is there public API to check if the component is mounted/unmounted

While this is the exact scenario isMounted() was originally created for, the current recommendation the usage of isMounted() is bad and will be removed in future React versions.

Instead, you should just check that any asynchrous functions are properly cancelled upon unmounting the component. If you use setTimeout, it's easy enough to just save the timeout identifier and cancel it in componentWillUnmount:

this.timeout = setTimeout(() => {
this.setState({ foo: 123 });
}, 5000);

...

componentWillUnmount() {
clearTimeout(this.timeout)
}

So to answer your question, there is no and there should be no API for checking if a component is mounted. This is because when unmounted, all references to the component should be cleared so that the garbage collector may remove it from memory.

React Native:How to detect function component will unmount?

Yoı must use useEffect for componentWillUnmount in functional components.

const MyCom = () => {

//do something here. ex, open gallery to upload image, zoon in image in

useEffect(() => {
// Component Did Mount

return => {
// ComponentWillUnmount
}
},[])

return(/*Component*/)
}

Why does a React component unmount but jsx doesn't

I think your question is about the difference between the two buttons' behavior, one is coming back to zero after clicking to rerender the parent component and the other not.

First of all, we should understand the life-cycle of a function component, the render is executing each state change.

function App() {
/**
* it will be executed each render
*/

return (<div />);
}

Also we have to understand the difference between create a component and instantiate a component.

 // this is creating a component
const ChildWrapper = () => <Child title="Component" />;

// this is instantiating a component
const childWrapperJsx = <Child title="jsx" />;

JSX is only a tool to transpile the syntaxe of this <Child title="jsx" /> to React.createElement('div', { title: "jsx" }) for example. To explain better, the code is transpiled to something like this:

 // this is creating a component
const ChildWrapper = () => React.createElement('button', { title: 'Component' });

// this is instantiating a component
const childWrapperJsx = React.createElement('button', { title: 'jsx' }) ;

Without going deep in the hole. In your implementation we have both components implemented into the render of the parent, like this.

function App() {
const ChildWrapper = () => <Child title="Component" />;
const childWrapperJsx = <Child title="jsx" />;

return (<div />);
}

Right now we realized that the first implementation is creating a new component each render, so that react can't memoize the component in the tree, it is not possible to do it.

// each render ChildWrapper is a new component. 
const ChildWrapper = () => <Child title="Component" />;

And the second one, the childWrapperJsx is already a react element instantiated and memoized. React will preserve the same instance on the parent component life cycle.

According to React's best practice, it is not recommended to create components inside the render of another component. If you try to put both implementations outside of the component, you will be able to see that both components won't be unmounted after the parent component's render.

const ChildWrapper = () => <Child title="Component" />; 
const childWrapperJsx = <Child title="jsx" />;

function App() {

return (
<>
<ChildWrapper />
{childWrapperJsx}
</>
);
}

How to stop memory leak on unmounted component

You arent actually setting the abortcontroller.signal to your axios.get call.

See these axios docs

axios.get('/foo/bar', {
signal: abortController.signal
}).then(...)
...
abortController.abort()


Related Topics



Leave a reply



Submit