Отмените все подписки в функции очистки useEffect, созданной Context.Consumer.
Каждый раз когда onClick
выполняет я получил предупреждение об утечке памяти. Как компонент может быть отписан от Context.Consumer в моем функциональном компоненте с useEffect
крючок?
Я не нашел способ как отписаться от AppContext. AppContext.unsubsribe()
не сработало.
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);
Сообщение об ошибке в консоли браузера:
Предупреждение. Невозможно выполнить обновление состояния React на отключенном компьютере.
составная часть. Это не работает, но это указывает на утечку памяти в вашем приложении. Чтобы исправить это, отмените все подписки и асинхронные задачи в функции очистки useEffect. в UserPage (создается Context.Consumer) в Route (создается withRouter(UserPage)) в withRouter(LoginPage) (создается Context.Consumer) в Route (создается UserRoute)
3 ответа
Ваша проблема в том, что axios возвращает обещание, поэтому, когда компонент монтируется, он выполняет axios.post(...)
по щелчку. Когда он ТОЛЬКО размонтируется (хотя обещание еще может быть "незавершенным"), setState
его finally
выполнит ПОСЛЕ того, как компонент размонтирован.
Вы можете легко проверить, смонтирован ли компонент:
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);
Благодаря @Bennet Dams я смог решить свою проблему, это мой код, следующий его примеру.
const isMounted = useRef(null);
useEffect(() => {
isMounted.current = true;
fetchRequestsData();
return () => {
isMounted.current = false;
};
}, []);
async function fetchRequestsData() {
//My previous code which led to the warning
/* const { data } = await axios({
url: '../api/random/my-requests',
method: 'get',
});
setSuspendedRequests(data.suspended); */
let data;
axios
.get('../api/random/my-requests')
.then((resp) => {
data = resp.data;
})
.catch((err) => console.log(err))
.finally(() => {
if (isMounted.current) {
setSuspendedRequests(data.suspended);
}
});
}
Как говорится в предупреждении, в вашем UserPage
компонент, который вы должны выполнить очистку useEffect
чтобы избежать утечек памяти.
Обратитесь к Документам, как требовать очистки после воздействия.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});