A callback function sets the component state. But sometimes subscription which supplies the data need to end. Because callback is executed asynchronously, it's not aware if the subscription ends just after making the service call (which executes the callback function).
Then I see following error in the console:
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 a useEffect cleanup function.
Is there a way to access the component state, even if I am in the callback function?
This would be the steps:
ReactJS – componentWillUnmount() Method This method is called during the unmounting phase of the React Lifecycle, i.e., before the component is destroyed or unmounted from the DOM tree. This method is majorly used to cancel all the subscriptions that were previously created in the componentWillMount method.
Using React DevTools to highlight what components rerendered To enable it, go to "Profiler" >> click the "Cog wheel" on the right side of the top bar >> "General" tab >> Check the "Highlight updates when components render." checkbox.
Using react-router you can easily prevent route change(which will prevent component unmount) by using Prompt . You need to manually pass the getUserConfirmation prop which is a function. You can modify this function as you like in any Router(Browser, Memory or Hash) to create your custom confirmation dialog(eg.
You can use a ref like this:
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
Then in your callback you can check if mounted.current === false
and avoid setting the state
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]
);
};
This hook (insired from Mohamed answer) solves the problem in a more elegant maner:
function useMounted() {
const mounted = useMemo(() => ({ current: true }), []);
useEffect(() => {
return () => { mounted.current = false}
}, [mounted]);
return mounted;
}
(Updated to use useMemo instead of useRef for readability).
You can return a function from useEffect, which will be fired when a functional component unmount. Please read this
import React, { useEffect } from 'react';
const ComponentExample = () => {
useEffect(() => {
// Anything in here is fired on component mount.
return () => {
// Anything in here is fired on component unmount.
}
}, [])
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With