Search code examples
javascriptreactjsrendersetstate

setState is not permanent


I am trying to create a sidemenu which has collapsible options.

Below is my code:

export default class CRSideMenu extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      fprActive: true
    };
    autoBind(this);
  }

  toggleFPR() {
    console.log('Setting fprActive from ', this.state.fprActive, ' to ', !this.state.fprActive);
    this.setState({
      fprActive: !this.state.fprActive
    });
  }

  render() {
    console.log('render', this.state.fprActive);
    return (
      <ul className='list-group list-group-nav'>
        <li>
          <a 
            href='#' 
            className={classnames('list-group-item', this.state.fprActive && 'active', 'has-subnav')}
            onClick={this.toggleFPR} >
            FPR
          </a>
          <ul className='list-group list-group-nav'>
            <li>
              <a href='' className='list-group-item'>FR</a>
            </li>
          </ul>
        </li>
      </ul>
    );
  }
}

When I printed out the this.state.fprActive in the render() method, I see the following:

  • Setting fprActive from true to false
  • render false
  • render true

How come my fprActive is being automatically setback to 'true' when I click only once?


Solution

  • I can't replicate the problem at this end, but the symptom says that your page is being refreshed when you click the anchor because you're not preventing the default action. Have toggleFPR call preventDefault on the event object it receives:

    toggleFPR(event) {
    //        ^^^^^ ------------ ***
      event.preventDefault(); // ***
      console.log('Setting fprActive from ', this.state.fprActive, ' to ', !this.state.fprActive);
      this.setState({
        fprActive: !this.state.fprActive
      });
    }
    

    Separately: You're breaking one of the fundamental React rules here:

    console.log('Setting fprActive from ', this.state.fprActive, ' to ', !this.state.fprActive);
    this.setState({
      fprActive: !this.state.fprActive
    });
    

    When setting state based on existing state, you must use the callback version, not the version you pass an object into:

    this.setState(({fprActive}) => {
      console.log('Setting fprActive from ', fprActive, ' to ', !fprActive);
      return {fprActive: !fprActive};
    });
    

    If you don't, it'll work most of the time, and fail in hard-to-diagnose ways sometimes.