问题
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)
回答1:
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);
回答2:
As the warning states, in your UserPage
component you need to perform a cleanup on useEffect
to avoid memory leaks.
Refer to Docs how to require cleanup after an effect.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
来源:https://stackoverflow.com/questions/56584107/cancel-all-subscriptions-in-a-useeffect-cleanup-function-created-by-context-cons