2
votes

I'm using React 15.0.1 with react-router 2.2.1. I'm using App as parent component which renders children components passed from router handlers.

var App = React.createClass({

    getInitialState(){
        return {
            auth : false
        }
    },

    checkAuth(){
        var self = this;
        $.ajax({
            type : 'GET',
            url : '/auth',
            success : function(res){
                if(res){
                    self.setState({
                        auth : true
                    });
                }else{
                    self.setState({ auth : false});
                }
            }
        });
    },

    render() {
        return(
            <div className="appWrapper">
                <Navbar auth={this.state.auth}/>
                <div className="container">
                    {this.props.children}
                </div>
            </div>
        );
    }
});

This is my Navbar component.

var Navbar = React.createClass({

    getInitialState(){
        return{
            user_actions : '' ,
            auth : this.props.auth
        }
    },

    componentDidMount(){
        this.checkAuth();
    },

    componentWillReceiveProps(nextProps){
        this.setState({
            auth : nextProps.auth
        }, function(){
            this.checkAuthState();
        });

        console.log(nextProps.auth);
    },

    checkAuth(){
        var self = this;
        $.ajax({
            type : 'GET',
            url : '/auth',
            success : function(res){
                if(res){
                    self.setState({
                        auth : true
                    });
                }else{
                    self.setState({ auth : false});
                }
                self.checkAuthState();
                console.log(self.state);
            }
        });
    },

    checkAuthState(){

        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>
        );
    }
});

These are my route handlers. When a user lands on route, they will be checked for authentication. If authentication fails, they will be redirected to login route.

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>
);

When app component mounts with Navbar and children component for the first time, it checks for authentication and adds links to the navbar accordingly. When user redirect from login route to index route after successful login, the children component is replaced with a different component, and that component's life cycle methods are called but Navbar doesn't re-renders. How can i re-render Navbar component so that it can again check for authentication and make changed to navbar links?

2

2 Answers

2
votes

I figured it out, i passed a method from App component to children component.

var App = React.createClass({

    getInitialState(){
        return {
            auth : false
        }
    },

    checkAuth(){
        var self = this;
        $.ajax({
            type : 'GET',
            url : '/auth',
            success : function(res){
                if(res){
                    self.setState({
                        auth : true
                    });
                }else{
                    self.setState({ auth : false});
                }
            }
        });
    },

    updateNavbar(){
        this.checkAuth();
    },

    render() {
        return(
            <div className="appWrapper">
                <Navbar auth={this.state.auth}/>
                <div className="container">
                    {React.cloneElement(this.props.children, {update: this.updateNavbar})}
                </div>
            </div>
        );
    }
});

When children component, for instance Login authenticates user, it calls update method passed as prop that will re-check for authentication, and makes change to App components auth state, that will trigger componentWillReceiveProps method on Navbar component with new props which will change state and re-render Navbar component. That's what i was trying to achieve.

var Login    =   React.createClass({
    getInitialState(){
        return {
            user : {email  : '', password : ''},
            auth_redirect : '',
            errors  : {},
            message : ''
        }
    },

    componentWillMount(){...},

    registerFormIsValid : function(){....},

    validateUser : function(){
        var self = this;
        $.ajax({
            type : 'POST',
            url : '/login',
            data : {
                email : this.state.user.email,
                password : this.state.user.password
            },
            success : function(res){
                if(!res.auth){
                    self.setState({
                        message : <Alert message="Invalid Email or Password!"/>
                    });
                }else{
                    self.props.update(); // Update Navbar 
                    self.context.router.push('/');
                }
            }
        });
    },

    Authenticate : function(event){....},

    saveUserState : function(event){...},

    render : function(){
        return (
            <div>
                <div className="login container">
                    <LoginForm
                        user={this.state.user}
                        onChange={this.saveUserState}
                        onSave={this.Authenticate}
                        errors={this.state.errors} />
                    {this.state.message}
                </div>
            </div>
        );
    }
});

Now i can update my Navbar component links from any children component passed down via route handler by calling this.props.update().

0
votes

You need to abstract auth logic in separate service, something like

auth = {
  check() {
    var self = this;
    $.ajax({
      type : 'GET',
      url : '/auth',
      success : function(res){
        if(res){
          onChange(true)
        }else{
          onChange(false);
        }
      }
    });
  },

  onChange() {}
}

Then in you App component you can do

updateAuth(isLoggedIn) {
  self.setState({auth: isLoggedIn});
}

componentWillMount() {
  auth.onChange = this.updateAuth;
}

And in each route where you need to check auth you add onEnter={auth.check} Or you can store data in localstorage to avoid calling api every time.

See basic example of auth flow with react-router