When use ComponentDidMount() I found this error :

2019-01-27 08:11发布

I found this error :

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.

Context : When I'm connected, I'm on the homepage, this page not contain the breadCrumb, but If I go on CampaignPage (also the name of the component), I have the BreadCrumb (Component name) I found this error.

On other post what I could see, they said probably problem on asynchronously on ComponentWillMount but I think my problem is different and I can't find a solution.

My code look like that :

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import objectAssign from 'object-assign';
import { withRouter } from 'react-router';
import {
  BREADCRUMBS_ROUTES,
  BREADCRUMBS_ROUTES_FOR_ID,
  BREADCRUMBS_ENDPOINT
} from 'constants';
import { getEntityById, setUpdatedBreadcrumbs } from 'actions/breadcrumbs';

import style from './style.scss';

class Breadcrumbs extends Component {
  constructor(props) {
    super(props);

    this.state = {
      breadcrumbs: [],
      names: {}
    };

    this.setBreadcrumbs = this.setBreadcrumbs.bind(this);
    this.loadEntityNameById = this.loadEntityNameById.bind(this);
  }
  componentWillMount() {
    this.setBreadcrumbs();
  }

  componentWillReceiveProps(nextProps) {
    const { isWillUpdate: newIsWillUpdate } = nextProps;
    const { isWillUpdate, saveUpdatedBreadcrumbs } = this.props;

    if (isWillUpdate === false && newIsWillUpdate === true) {
      this.setBreadcrumbs();
      saveUpdatedBreadcrumbs();
    }
  }


  setBreadcrumbs() {
    const { params, path } = this.props.match;
    let currentPath = '';

    const pathSplitedAndExtendet = path.split('/')
      .filter(item => !!item)
      .map(item => {
        if (item[0] === ':' && item.slice(1) !== 'adPage') {
          const parameterName = item.slice(1);
          this.loadEntityNameById(
            parameterName,
            params[parameterName]
          );

          return {
            route: `/${params[parameterName]}${BREADCRUMBS_ROUTES_FOR_ID[parameterName]}`,
            parameter: parameterName
          };
        }
        return {
          route: `/${item}`,
          parameter: ''
        };
      });

    const breadcrumbs = pathSplitedAndExtendet
      .filter(item => item.parameter !== 'adPage')
      .map((item) => {
        const indexOfRoute = currentPath.indexOf(item.route);
        if (currentPath.slice(indexOfRoute) !== item.route) {
          currentPath = `${currentPath}${item.route}`;
        }

        return ({
          ...item,
          name: BREADCRUMBS_ROUTES[item.route] || '',
          route: currentPath
        });
      });
    this.setState({ breadcrumbs });
  }

  async loadEntityNameById(parameter, id) {
    const { loadEntityById } = this.props;
    await loadEntityById(BREADCRUMBS_ENDPOINT[parameter], id)
      .then((data) => {
        this.setState({ names: objectAssign(this.state.names, { [parameter]: { id, name: data.name } }) });
      });
  }

  render() {
    const { breadcrumbs, names } = this.state;
    const { showBreadcrumbs } = this.props;
    return (
      <div className={style.breadcrumbs}>
        {
          showBreadcrumbs && breadcrumbs
            .map((item, index) => {
              return (
                <div
                  key={`${item.name}--${item.route}--${index}`}
                  className={classnames(style.bread, index === breadcrumbs.length - 1 ? style.last : null)}
                  role="link"
                  tabIndex={-10 - index}
                  onKeyDown={() => {}}
                  onClick={item.route ? () => this.props.history.push(item.route) : null}
                >
                  {`${item.name || (names[item.parameter]
                    ? names[item.parameter].name : '...')}
                    ${((breadcrumbs.length > 1) && (index !== breadcrumbs.length - 1)) ? ' >' : ''}
                  `}
                </div>
              );
            })
        }
      </div>
    );
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Breadcrumbs));

3条回答
女痞
2楼-- · 2019-01-27 08:40

You cannot call setState in componentWillMount try using componentDidMount instead

查看更多
SAY GOODBYE
3楼-- · 2019-01-27 08:44

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.

This error message clearly states that your application has memory leak. What's going on here exactly?

Well, you're using async operation (loadEntityNameById) in setBreadcrumbs method. Which is being called in componentWillMount and in componentWillReceiveProps. This means when you go from CampaignPage component to BreadCrumb component, it will do the async operation ie. loadEntityNameById is running in the background which only sets the state once it's finished. But until that time your BreadCrumb component might be unmounted. The react application doesn't allow you to update the state on an unmounted component.

Furthermore, you should not use componentWillMount method at all. Use componentDidMount hook instead.

To fix the issue, what you can do is to set a flag like:

componentDidMount() {
  // component is mounted, set the didMount property on BreadCrumb component
  // you can use any name instead of didMount what you think is proper
  this.didMount = true
  // now, you can update the state
  if(this.didMount) { // be sure it's not unmounted
    this.setState({names: ...})
  }

Next, you should clear the didMount property when the component is unmounted.

componentWillUnmount() {
  this.didMount = false // component is unmounted

This will ensure you that your application memory will not be leaked. Because, we properly setting the state when required but not when it doesn't require, and stopping unnecessary loop.

查看更多
迷人小祖宗
4楼-- · 2019-01-27 08:55

You're doing an asynchronous action (loadEntityNameById) that sets the state for the component when it finishes. By that time, your Breadcrumbs component may have been unmounted, and the error is thrown.

查看更多
登录 后发表回答