Each time when onClick
executes I received a warning message about memory leak. How component can be can unsubscribed from the Context.Consumer in my functional component with useEffect
hook?
I did not find a way how to unsubscribe from the AppContext. AppContext.unsubsribe()
did not work.
import React, {useState, useContext} from 'react';
import {withRouter} from 'react-router-dom';
import axios from 'axios';
import {AppContext} from "../context/AppContext";
const LoginPage = (props) => {
const [name, setName] = useContext(AppContext);
const [isLoading, setIsLoading] = useState(false);
const onClick = () => {
setIsLoading(true);
axios.post('/get-name')
.then(resp => {
setName(resp);
setIsLoading(false);
props.history.push('/');
})
.catch(err => console.log(err))
.finally(() => setIsLoading(false));
};
return (
<div>
<button onClick={onClick}></button>
</div>
);
};
export default withRouter(LoginPage);
Error message in the browser 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. in UserPage (created by Context.Consumer) in Route (created by withRouter(UserPage)) in withRouter(LoginPage) (created by Context.Consumer) in Route (created by UserRoute)
The hook comes with a cleanup function, which you might not always need, but it can come in handy. To invoke the cleanup function you can simply add a return function like so: useEffect(() => { // Your effect return () => { // Cleanup }; }, []); The cleanup can prevent memory leaks and remove unwanted things.
To run the useEffect hook callback only once when the component mounts, we just have to pass in an empty array into the 2nd argument of useEffect hook. We just pass in an empty array into useEffect and the callback would only run once.
Your problem is that axios is returning a promise, so when the component is mounted, it executes axios.post(...)
on click. When it THEN unmounts (while the promise could still be "unfinished"), the setState
of its finally
will execute AFTER the component unmounted.
You can use an easy check if the component is mounted:
import React, {useState, useContext, useEffect} from 'react';
import {withRouter} from 'react-router-dom';
import axios from 'axios';
import {AppContext} from "../context/AppContext";
const LoginPage = (props) => {
const [name, setName] = useContext(AppContext);
const [isLoading, setIsLoading] = useState(false);
const isMounted = useRef(null);
useEffect(() => {
// executed when component mounted
isMounted.current = true;
return () => {
// executed when unmount
isMounted.current = false;
}
}, []);
const onClick = () => {
setIsLoading(true);
axios.post('/get-name')
.then(resp => {
setName(resp);
setIsLoading(false);
props.history.push('/');
})
.catch(err => console.log(err))
.finally(() => {
if (isMounted.current) {
setIsLoading(false)
}
});
};
return (
<div>
<button onClick={onClick}></button>
</div>
);
};
export default withRouter(LoginPage);
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