Calling setState in render is not avoidable
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 />;
}
}
});