У меня 2 компонента:
Заказы - получить данные и отобразить их.
ErrorHandler - в случае возникновения ошибки на сервере модальное окно покажет и отобразит сообщение.
Компонент ErrorHandler искажает компонент заказа
Я использую пакет axios для загрузки данных в компонент «Заказы», и я использую перехватчики axios для установки состояния ошибки и извлечения после размонтирования компонента.
Когда я перехожу к компонентам заказов назад и вперед, я иногда получаю сообщение об ошибке в консоли:
Warning: Can't call setState (or forceUpdate) 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.
in Orders (at ErrorHandler.jsx:40)
in Auxiliary (at ErrorHandler.jsx:34)
in _class2 (created by Route)
Я попытался решить это своим предыдущим случаем React Warning: можно обновить только смонтированный или монтируемый компонент, но здесь я не могу сделать токен axios инспекторами. Кто-нибудь раньше решал эту проблему?
Вот мои компоненты:
Заказы:
import React, { Component } from 'react';
import api from '../../api/api';
import Order from '../../components/Order/Order/Order';
import ErrorHandler from '../../hoc/ErrorHandler/ErrorHandler';
class Orders extends Component {
state = {
orders: [],
loading: true
}
componentDidMount() {
api.get('/orders.json')
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
});
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key = {order.id}
ingrediencies = {order.ingrediencies}
price = {order.price} />);
})}
</div>
);
}
}
export default ErrorHandler(Orders, api);
ErrorHandler:
import React, { Component } from 'react';
import Auxiliary from '../Auxiliary/Auxiliary';
import Modal from '../../components/UI/Modal/Modal';
const ErrorHandler = (WrappedComponent, api) => {
return class extends Component {
requestInterceptors = null;
responseInterceptors = null;
state = {
error: null
};
componentWillMount() {
this.requestInterceptors = api.interceptors.request.use(request => {
this.setState({ error: null });
return request;
});
this.responseInterceptors = api.interceptors.response.use(response => response, error => {
this.setState({ error: error });
});
}
componentWillUnmount() {
api.interceptors.request.eject(this.requestInterceptors);
api.interceptors.response.eject(this.responseInterceptors);
}
errorConfirmedHandler = () => {
this.setState({ error: null });
}
render() {
return (
<Auxiliary>
<Modal
show = {this.state.error}
modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props} />
</Auxiliary>
);
}
};
};
export default ErrorHandler;
Не помогло .. до сих пор иногда выдает ошибку ... :(
Создайте React jsFiddle, странно, что такая же ошибка отображается на componentDidMount



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы не можете установить состояние в методе componentWillMount. Попробуйте пересмотреть логику своего приложения и перенести ее в другой метод жизненного цикла.
Я не устанавливаю там состояние, я просто делаю функцию настройки, которая устанавливает состояние, но это происходит после. Но я также попытался изменить его на componentDidMount, и ошибка все еще существует.
Посмотрите на свой ErrorHandler. Вы устанавливаете там состояние. И трассировка стека ошибок сообщает вам следующее:
Я думаю, что из-за асинхронного вызова, который запускает setState, это может произойти, даже если компонент не смонтирован. Чтобы этого не случилось, вы можете использовать какие-то флаги:
state = {
isMounted: false
}
componentDidMount() {
this.setState({isMounted: true})
}
componentWillUnmount(){
this.state.isMounted = false
}
А позже оберните вызовы setState с if:
if (this.state.isMounted) {
this.setState({ loading: false, orders: fetchedOrders });
}
Изменить - пример добавления функционального компонента:
function Component() {
const [isMounted, setIsMounted] = React.useState(false);
useEffect(() => {
setIsMounted(true);
return () => {
setIsMounted(false);
}
}, []);
return <div></div>;
}
export default Component;
Из документации по реакции: вы не должны вызывать setState () в componentWillUnmount (), потому что компонент никогда не будет повторно отрисован. После того, как экземпляр компонента будет размонтирован, он больше никогда не будет монтироваться.
Правильно, пропустил ... Я обновил фиктивный пример
Разве это не избавило бы от ошибки, если бы состояние не было задано, и, следовательно, не нарушило бы намеченную функциональность?
Предполагаемая функциональность предназначена только для смонтированных компонентов. Если компонент не смонтирован, значит, функциональность не предусмотрена. Фактически в этом случае единственное жизнеспособное решение - это то, что представлено здесь.
@ Goran.it А как насчет функционального компонента?
Я думаю, что основная причина та же, что я ответил вчера, вам нужно «отменить» запрос на unmount, я не вижу, делаете ли вы это для вызова api.get() в компоненте Orders.
Замечание об обработке ошибок: это выглядит слишком сложным, я определенно рекомендую посмотреть ErrorBoundaries, предоставленный React. Вам не нужно иметь interceptors или компонент более высокого порядка.
Для ErrorBoundaries React представил метод жизненного цикла под названием componentDidCatch.
Вы можете использовать его для упрощения кода ErrorHandler, чтобы:
class ErrorHandler extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true, errorMessage : error.message });
}
render() {
if (this.state.hasError) {
return <Modal
modalClosed = {() => console.info('What do you want user to do? Retry or go back? Use appropriate method logic as per your need.')}>
{this.state.errorMessage ? this.state.errorMessage : null}
</Modal>
}
return this.props.children;
}
}
Затем в вашем компоненте Orders:
class Orders extends Component {
let cancel;
state = {
orders: [],
loading: true
}
componentDidMount() {
this.asyncRequest = api.get('/orders.json', {
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
cancel = c;
})
})
.then(response => {
const fetchedOrders = [];
if (response && response.data) {
for (let key in response.data) {
fetchedOrders.push({
id: key,
...response.data[key]
});
}
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch(error => {
this.setState({ loading: false });
// please check the syntax, I don't remember if it is throw or throw new
throw error;
});
}
componentWillUnmount() {
if (this.asyncRequest) {
cancel();
}
}
render() {
return (
<div>
{this.state.orders.map(order => {
return (<Order
key = {order.id}
ingrediencies = {order.ingrediencies}
price = {order.price} />);
})}
</div>
);
}
}
И используйте его в своем коде как:
<ErrorHandler>
<Orders />
</ErrorHandler>
использовать componentDidMount