OM The Eternity
OM The Eternity

Reputation: 16204

setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor)

I have component as below:

const VIEW_PROFILE = 'profile'
const VIEW_SAA = 'saa'
const VIEW_DOCUMENTS = 'documents'
const VIEW_FINANCIERS = 'viewFinancierInfo'

class ViewFinancier extends Component {
  constructor(props) {
    super(props)
    this.state = {
      selectedNavigation: VIEW_PROFILE,
      financierId: this.props.location.state.financierDetails.financierId,
      defaultLoading: false
    }

    this.handleChangeNav = this.handleChangeNav.bind(this)
    this.handleCancelButton = this.handleCancelButton.bind(this)
  }

  componentWillMount(props) {
    this.props.loadFinancierProfile(this.props.location.state.financierDetails.financierId)
  }

  componentWillReceiveProps(newProps) {
    this.setState({
      defaultLoading: true,
      viewfinancierprofiledata: newProps.viewfinancierprofiledata
    })
  }

  handleChangeNav(e) {
    var selectedNavigation = e.target.attributes.getNamedItem('value').value
    this.setState({
      selectedNavigation: selectedNavigation
    })
  }

  handleCancelButton(changingState) {
    this.setState({
      selectedNavigation: changingState
    })
  }

  render() {
    if (this.state.defaultLoading === false) {
      return (
        <div className="tabledataloading tabletext">
          Please wait while the data is loading <img alt="Loading..." src={loadingimg} />
        </div>
      )
    } else if (this.state.selectedNavigation === VIEW_FINANCIERS) {
      this.props.history.push('/financier/')
      return null
    } else {
      return (
        <div id="lesse-info-component-wrapper" className="melody-common-container">
          <div id="header-wrapper">
            <h1 className="melody-common-module-title">VIEW FINANCIER INFO</h1>
          </div>
          <div id="navigation-wrapper">
            <ul id="add-financier-info-nav" className="topnavpad">
              <li
                value={VIEW_PROFILE}
                onClick={this.handleChangeNav}
                className={'add-financier-info-nav-item ' + (this.state.selectedNavigation === VIEW_PROFILE ? 'active' : '')}>
                PROFILE
              </li>
              <li
                value={VIEW_SAA}
                onClick={this.handleChangeNav}
                className={'add-financier-info-nav-item ' + (this.state.selectedNavigation === VIEW_SAA ? 'active' : '')}>
                SAA
              </li>
              <li
                value={VIEW_DOCUMENTS}
                onClick={this.handleChangeNav}
                className={'add-financier-info-nav-item ' + (this.state.selectedNavigation === VIEW_DOCUMENTS ? 'active' : '')}>
                DOCUMENTS
              </li>
            </ul>
          </div>
          {this.state.selectedNavigation === VIEW_PROFILE
            ? <ViewFinancierProfile financierId={this.props.financierId} onCancelHandler={this.handleCancelButton} />
            : null}
          {this.state.selectedNavigation === VIEW_SAA
            ? <ViewFinancierSAA financierId={this.props.financierId} onCancelHandler={this.handleCancelButton} />
            : null}
          {this.state.selectedNavigation === VIEW_DOCUMENTS
            ? <ViewFinancierDocument financierId={this.props.financierId} onCancelHandler={this.handleCancelButton} />
            : null}
        </div>
      )
    }
  }
}

const mapStateToProps = state => {
  return {
    viewfinancierprofiledata: state.viewfinancierprofiledata
  }
}

const mapDispatchToProps = dispatch => {
  return {
    loadFinancierProfile: financierId => dispatch(viewFinancierProfileAction.loadFinancierProfile(financierId))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ViewFinancier)

And on execution I am getting the warning:Warning: setState(...): Cannot update during an existing state transition (such as withinrenderor another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved tocomponentWillMount`

On looking into the console I found that the snippet this.props.history.push('/financier/') is throwing the error.

On further research in other similar questions in Stack Overflow, I also found that this actually setting the parents state under render method, which is not allowed.

But now I cannot figure out how I can achieve the condition I am looking for.

Upvotes: 0

Views: 522

Answers (1)

An Nguyen
An Nguyen

Reputation: 1478

Component props should not be mutated by itself. I think there are 2 solutions for you in this situation.

  1. Copy this.props.history to state, and mutate history by calling setState. Put the checking of selectedNavigation outside of render. For example (just my idea, please modify it base on your application logical):

class ViewFinancier extends Component {
  constructor(props) {
    super(props)
    this.state = {
      ...
      history = props.history,
      ...
    }
    ...
  }

  componentWillReceiveProps(newProps) {
    this.setState({
      history: newProps.history,
      ...
    })
  }
  
  handleChangeNav(e) {
    var selectedNavigation = e.target.attributes.getNamedItem('value').value;
    
    this.setState({
      selectedNavigation: selectedNavigation      
    })
    
    this.historyCheck(selectedNavigation);
  }

  handleCancelButton(changingState) {
    this.setState({
      selectedNavigation: changingState
    });
    
    this.historyCheck(changingState);
  }
  
  historyCheck(nav) {
    if (nav === VIEW_FINANCIERS) {
      history.push('/financier/');
    };
    
    this.setState(history: history);
  }

  render() {    
    ...
    } else if (this.state.selectedNavigation === VIEW_FINANCIERS) {
      return null
    }
    ...
  }
}

  1. If this.props.history is used by parent component also. Then you can pass a function to update history from parent component and pass this function to your current (child) component.

class ParentFinancier extends Component {
  pushHistory => (val) {
    let {history} = this.state;
    this.setState(history: history.push('/financier/'));
  }
  
  render() {
    ...
    <ViewFinancier pushHistory={this.pushHistory} ... />
  }
  ...
}



class ViewFinancier extends Component {
  ...
  } else if (this.state.selectedNavigation === VIEW_FINANCIERS) {
    this.props.pushHistory('/financier/')
    return null   
  }
  ...
}

...

Upvotes: 1

Related Questions