I'm using react with react-router. After checking authentication with onEnter Asynchronous hook on IndexRoute, App component gets rendered. App component has an initial state auth which is set to undefined when it renders. auth state is being passed to Navbar component as prop where it will be used to decide whether or not to show login, register and logout links.
When App component is done rendering, componentDidMount()
makes an ajax call to again check if user is authenticated. On response it makes change to the state. After state change from ajax request, i'm logging state to console, this.setState()
method is not changing state but somehow still triggers componentWillReceiveProps() method on Navbar component and this.props.auth
value is still undefined.
// Checks Authentication Asynchronously
isAuthenticated(nextState, replace, callback) {
$.ajax({
type : 'GET',
url : '/auth',
success : function(res){
if(!res){
callback(replace({ pathname: '/login', query: { auth: 'false' } }));
}else{
callback();
}
}
});
};
// routes
var routes = (
<Router history={browserHistory}>
<Route path="/" component={require('./components/app')}>
<IndexRoute component={require('./components/dashboard/index')} onEnter={Auth.isAuthenticated}/>
<Route path="/register"
component={require('./components/authentication/register')}
onEnter={Auth.isNotAuthenticated} />
<Route path="/login"
component={require('./components/authentication/login')}
onEnter={Auth.isNotAuthenticated}/>
<Route path="*"
component={require('./components/404/404')}/>
</Route>
</Router>
);
// App
const App = React.createClass({
getInitialState(){
return {
auth : undefined
}
},
componentDidMount(){
console.log('App componentDidMount');
this.checkAuth();
},
checkAuth(){
var self = this;
$.ajax({
type : 'GET',
url : '/auth',
success : function(res){
if(res){
self.setState({
auth : true
});
}else{
self.setState({ auth : false});
}
}
});
console.log(this.state.auth);
},
render() {
return(
<div className="appWrapper">
<Navbar auth={this.state.auth}/>
<div className="container">
{this.props.children}
</div>
</div>
);
}
});
// Navbar
var Navbar = React.createClass({
getInitialState(){
return{
user_actions : '' ,
auth : this.props.auth
}
},
componentDidMount(){
console.log('Navbar componentDidMount ', this.props.auth);
this.checkAuthState();
},
componentWillReceiveProps(){
console.log('Navbar componentWillReceiveProps ', this.props.auth);
this.setState({
auth : this.props.auth
});
this.checkAuthState();
},
checkAuthState(){
console.log('Nav Mounted with auth : ', this.state.auth);
if(this.state.auth == undefined){
this.state.user_actions = '';
}
if(!this.state.auth){
this.state.user_actions = <ul className="nav navbar-nav navbar-right">
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
</ul>;
this.setState({
user_actions : this.state.user_actions
});
}
if(this.state.auth){
this.state.user_actions = <ul className="nav navbar-nav navbar-right">
<li><a href="/logout">Logout</a></li>
</ul>;
this.setState({
user_actions : this.state.user_actions
});
}
},
render : function(){
return (
<nav className="navbar navbar-default">
<div className="container">
<a href="/" className="navbar-brand">Reactor</a>
{this.state.user_actions}
</div>
</nav>
);
}
});
First of all, I suggest you to reread React.JS documentation, because there are couple of things that need to be noted:
- Never mutate
this.state
directly, usesetState
method instead.(line: 108, 111, 121, 133, 136, 146)
- You should use state for storing data that changes over time, not an element.
(line: 111, 121, 136, 146)
tl;dr; Let's go back to the questions:
You won't see it if you print the value after ajax request! The reason is:
First, you're doing asynchronous request using Ajax and trying to see the result in synchronous way. JS will execute your console.log
first which still contains the value before request, and then perform ajax request callback. This is the block of your code:
$.ajax({ ...,
success: function(res) {
if(res) { self.setState({ auth : true }); }/
...
} // will executed later (after ajax get response)
});
console.log(this.state.auth); // will executed first, this is why it always prints the value as undefined
Second, you won't able to see the changed state value right after you set a new state value. For instance, let say the value of this.state.auth
is false
:
this.setState({ auth: true});
console.log(this.state.auth); // will print false, instead of true as your new value
You're able to see your new state value by using componentWillUpdate(nextProps, nextState)
method. You can read about this from this link: React.JS Component Specs and Lifecycle
It means that your state value is successfully changed by setState()
on your ajax response. The proof is Navbar component receive a new props that send it down by App component (where the auth state is changed) which will trigger componentWillReceiveProps()
method.
Maybe your code should be like this:
// App
const App = React.createClass({
getInitialState : function(){
return {
auth : false
}
},
componentDidMount : function() {
console.log('App componentDidMount');
this.checkAuth();
},
componentWillUpdate : function(nextProps, nextState) {
//you'll see the changing state value in here
console.log('Your prev auth state: ' + this.state.auth);
console.log('Your next auth state: ' + nextState.auth);
},
checkAuth : function(){
var self = this;
$.ajax({
type : 'GET',
url : '/auth',
success : function(res){
if(res){
self.setState({ auth : true });
}
}
});
},
render : function(){
return(
<div className="appWrapper">
<Navbar auth={this.state.auth}/>
<div className="container">
{this.props.children}
</div>
</div>
);
}
});
// Navbar
// Because the navbar component receive data (this.props.auth) from parent (app) via props, so we're no longer need to assign auth as a state in Navbar component.
const Navbar = React.createClass({
render : function(){
// you're no longer need checkAuthState method
let navItems;
if(!this.props.auth){
navItems = (<ul className="nav navbar-nav navbar-right">
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
</ul>);
} else {
navItems = (<ul className="nav navbar-nav navbar-right">
<li><a href="/logout">Logout</a></li>
</ul>);
}
return (
<nav className="navbar navbar-default">
<div className="container">
<a href="/" className="navbar-brand">Reactor</a>
{ navItems }
</div>
</nav>
);
}
});
Hope it helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With