Reputation: 1233
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.
Upvotes: 111
Views: 148478
Reputation: 1
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
});
Upvotes: 0
Reputation: 383
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);
}
Upvotes: 1
Reputation: 475
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!
Upvotes: 0
Reputation: 131
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);
};
}, []);
Upvotes: 0
Reputation: 4957
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) ...
Upvotes: 58
Reputation: 4341
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
}
Upvotes: 15
Reputation: 11650
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;
Upvotes: 4
Reputation: 281864
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.
Upvotes: 144
Reputation: 190
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..
Upvotes: -1
Reputation: 121
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 () { /* ... */ }
}
Upvotes: 1
Reputation: 1342
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} />
);
}
}
Upvotes: 23
Reputation: 853
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 =]
Upvotes: 4
Reputation: 5166
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.
Upvotes: 26
Reputation: 179
i found that the component will be unmounted, generate fill this var
if(!this._calledComponentWillUnmount)this.setState({vars});
Upvotes: -1