21

React document states that the render function should be pure which mean it should not use this.setState in it .However, I believe when the state is depended on 'remote' i.e. result from ajax call.The only solution is setState() inside a render function

In my case.Our users can should be able to log in. After login, We also need check the user's access (ajax call )to decide how to display the page.The code is something like this

React.createClass({
     render:function(){
          if(this.state.user.login)
          {
              //do not call it twice
              if(this.state.callAjax)
              {
              var self=this
              $.ajax{
                  success:function(result)
                  {
                      if(result==a) 
                      {self.setState({callAjax:false,hasAccess:a})}
                      if(result==b) 
                      {self.setState({callAjax:false,hasAccess:b})}

                  }
              }
              }
              if(this.state.hasAccess==a) return <Page />
              else if(this.state.hasAccess==a) return <AnotherPage />
              else return <LoadingPage />
          }
          else
          {
            return <div>
                   <button onClick:{
                   function(){this.setState({user.login:true})}
                   }> 
                   LOGIN
                   </button>
                   </div>
          }
     }
})

The ajax call can not appear in componentDidMount because when user click LOGIN button the page is re-rendered and also need ajax call .So I suppose the only place to setState is inside the render function which breach the React principle

Any better solutions ? Thanks in advance

Guichi
  • 2,150
  • 1
  • 19
  • 27
  • Call `$.ajax` wherever you call `this.setState({ callAjax: true })`. – Pavlo Feb 09 '16 at 11:10
  • @Pavlo "callAjax" is initially true, I set it as false because I do not want to call in second time. Or it will be a infinite loop of render and setState – Guichi Feb 09 '16 at 11:22
  • Could you provide a complete example, including component usage? – Pavlo Feb 09 '16 at 11:38
  • @Pavlo thank you. i suppose i already kown the anwser. setState in render function do not have any problem as long as in a async way. such as event listener(normal way) or ajax call .i just tested that if you call setstate directly in render.exception will be thrown. if you wrap it in a seTimeout callback.it is fine – Guichi Feb 09 '16 at 11:46
  • Here is an example of right AJAX calls in component https://facebook.github.io/react/tips/initial-ajax.html – xCrZx Feb 09 '16 at 11:48
  • @xCrZx thanks for the example. but different cases.in that example ajax call only need to occur at the beginning.in my case I can not call ajax when componentDidMount when the user maybe not available. I should wait for something happens(user click Login button) – Guichi Feb 09 '16 at 11:54

1 Answers1

34

render should always remain pure. It's a very bad practice to do side-effecty things in there, and calling setState is a big red flag; in a simple example like this it can work out okay, but it's the road to highly unmaintainable components, plus it only works because the side effect is async.

Instead, think about the various states your component can be in — like you were modeling a state machine (which, it turns out, you are):

  • The initial state (user hasn't clicked button)
  • Pending authorization (user clicked login, but we don't know the result of the Ajax request yet)
  • User has access to something (we've got the result of the Ajax request)

Model this out with your component's state and you're good to go.

React.createClass({
  getInitialState: function() {
    return {
      busy: false, // waiting for the ajax request
      hasAccess: null, // what the user has access to
      /**
       * Our three states are modeled with this data:
       *
       * Pending: busy === true
       * Has Access: hasAccess !==  null
       * Initial/Default: busy === false, hasAccess === null
       */
    };
  },

  handleButtonClick: function() {
    if (this.state.busy) return;

    this.setState({ busy: true }); // we're waiting for ajax now
    this._checkAuthorization();
  },

  _checkAuthorization: function() {
    $.ajax({
      // ...,
      success: this._handleAjaxResult
    });
  },

  _handleAjaxResult: function(result) {
    if(result === a) {
      this.setState({ hasAccess: a })
    } else if(result ===b ) {
      this.setState({ hasAccess: b })
    }
  },

  render: function() {
    // handle each of our possible states
    if (this.state.busy) { // the pending state
      return <LoadingPage />;
    } else if (this.state.hasAccess) { // has access to something
      return this._getPage(this.state.hasAccess);
    } else {
      return <button onClick={this.handleButtonClick}>LOGIN</button>;
    }
  },

  _getPage: function(access) {
    switch (access) {
    case a:
      return <Page />;
    case b:
      return <AnotherPage />;
    default:
      return <SomeDefaultPage />;
    }
  }
});
Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
  • Looks indeed like a more extensive and complete answer. Tried to delete my original answer, but couldn't, as it was marked as the accepted answer :( – wintvelt Feb 09 '16 at 16:26
  • @wintvelt oh,the point of your answer is to call ajax inside the success callback of another ajax call,in this way ,there is no need to use a extra state like 'login' .However,I sort of distorted my case,the login credentials stored in localstorage if the user already.and we can check it by Parsen.User.current() (Parser is a sdk which will be retired soon ).in your solution what if the user already login just need to check access? so there should be a state to distinguish if the user login or not (Parse sdk can check localstorage )if the user login ,call ajax to check access automatically – Guichi Feb 09 '16 at 19:58
  • @wintvelt but your idea did inspire me.Here is the solution:In componentDidMount if, the user login ,call ajax to check access else nothing.when user click button .login first ,and in login callback ,call ajax to check user access!!! – Guichi Feb 09 '16 at 20:07
  • What if an ajax triggers a background task? – James Lin Jun 19 '19 at 22:46