最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React Warning: Can't call setState (or forceUpdate) on an unmounted component - Stack Overflow

programmeradmin3浏览0评论

I have 2 ponents:
Orders - fetch some data and display it.
ErrorHandler - In case some error happen on the server, a modal will show and display a message.
The ErrorHandler ponent is warping the order ponent

I'm using the axios package to load the data in the Orders ponent, and I use axios interceptors to setState about the error, and eject once the ponent unmounted.

When I navigate to the orders ponents back and forward i sometimes get an error in the console:

Warning: Can't call setState (or forceUpdate) on an unmounted ponent. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the ponentWillUnmount method.
    in Orders (at ErrorHandler.jsx:40)
    in Auxiliary (at ErrorHandler.jsx:34)
    in _class2 (created by Route)

I tried to solve it by my previous case React Warning: Can only update a mounted or mounting ponent but here I can't make an axios token by the inspectors. Has anyone solved this issue before?

Here are my ponents:

Orders:

import React, { Component } from 'react';
import api from '../../api/api';
import Order from '../../ponents/Order/Order/Order';
import ErrorHandler from '../../hoc/ErrorHandler/ErrorHandler';

class Orders extends Component {
    state = {
        orders: [],
        loading: true
    }

    ponentDidMount() {
        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 '../../ponents/UI/Modal/Modal';

const ErrorHandler = (WrappedComponent, api) => {
    return class extends Component {
        requestInterceptors = null;
        responseInterceptors = null;
        state = {
            error: null
        };

        ponentWillMount() {
            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 });
            });
        }

        ponentWillUnmount() {
            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;

I have 2 ponents:
Orders - fetch some data and display it.
ErrorHandler - In case some error happen on the server, a modal will show and display a message.
The ErrorHandler ponent is warping the order ponent

I'm using the axios package to load the data in the Orders ponent, and I use axios interceptors to setState about the error, and eject once the ponent unmounted.

When I navigate to the orders ponents back and forward i sometimes get an error in the console:

Warning: Can't call setState (or forceUpdate) on an unmounted ponent. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the ponentWillUnmount method.
    in Orders (at ErrorHandler.jsx:40)
    in Auxiliary (at ErrorHandler.jsx:34)
    in _class2 (created by Route)

I tried to solve it by my previous case React Warning: Can only update a mounted or mounting ponent but here I can't make an axios token by the inspectors. Has anyone solved this issue before?

Here are my ponents:

Orders:

import React, { Component } from 'react';
import api from '../../api/api';
import Order from '../../ponents/Order/Order/Order';
import ErrorHandler from '../../hoc/ErrorHandler/ErrorHandler';

class Orders extends Component {
    state = {
        orders: [],
        loading: true
    }

    ponentDidMount() {
        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 '../../ponents/UI/Modal/Modal';

const ErrorHandler = (WrappedComponent, api) => {
    return class extends Component {
        requestInterceptors = null;
        responseInterceptors = null;
        state = {
            error: null
        };

        ponentWillMount() {
            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 });
            });
        }

        ponentWillUnmount() {
            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;
Share Improve this question edited Apr 5, 2021 at 6:31 Or Assayag asked Jul 31, 2018 at 8:51 Or AssayagOr Assayag 6,34613 gold badges72 silver badges108 bronze badges 3
  • 1 use ponentDidMount – Shubham Agarwal Bhewanewala Commented Jul 31, 2018 at 9:04
  • Didn't help..still get the error sometimes... :( – Or Assayag Commented Jul 31, 2018 at 9:06
  • 1 Create a React jsFiddle, it's strange that the same error is shown on ponentDidMount – Paolo Dell'Aguzzo Commented Jul 31, 2018 at 9:09
Add a ment  | 

3 Answers 3

Reset to default 12

I think that's due to asynchronous call which triggers the setState, it can happen even when the ponent isn't mounted. To prevent this from happening you can use some kind of flags :

  state = {
    isMounted: false
  }
  ponentDidMount() {
      this.setState({isMounted: true})
  }
  ponentWillUnmount(){
      this.state.isMounted = false
  }

And later wrap your setState calls with if:

if (this.state.isMounted) {
   this.setState({ loading: false, orders: fetchedOrders });
}

Edit - adding functional ponent example:

function Component() {
  const [isMounted, setIsMounted] = React.useState(false);

  useEffect(() => {
    setIsMounted(true);
    return () => {
      setIsMounted(false);
    }
  }, []);

  return <div></div>;
}

export default Component;

You can't set state in ponentWillMount method. Try to reconsider your application logic and move it into another lifecycle method.

I think rootcause is the same as what I answered yesterday, you need to "cancel" the request on unmount, I do not see if you are doing it for the api.get() call in Orders ponent.

A note on the Error Handling, It looks overly plicated, I would definitely encourage looking at ErrorBoundaries provided by React. There is no need for you to have interceptors or a higher order ponent.

For ErrorBoundaries, React introduced a lifecycle method called: ponentDidCatch. You can use it to simplify your ErrorHandler code to:

class ErrorHandler extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  ponentDidCatch(error, info) {
    this.setState({ hasError: true, errorMessage : error.message });
  }

  render() {
    if (this.state.hasError) {
      return <Modal 
                    modalClosed={() => console.log('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;
  }
}

Then in your Orders Component:

class Orders extends Component {
    let cancel;
    state = {
        orders: [],
        loading: true
    }

    ponentDidMount() {
        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;
            });
    }

    ponentWillUnmount() {
       if (this.asyncRequest) {
          cancel();
       }
    }

    render() {
        return (
            <div>
                {this.state.orders.map(order => {
                    return (<Order
                        key={order.id}
                        ingrediencies={order.ingrediencies}
                        price={order.price} />);
                })}
            </div>
        );
    }
}

And use it in your code as:

<ErrorHandler>
   <Orders />
</ErrorHandler>
发布评论

评论列表(0)

  1. 暂无评论