Ad

How Do You Pass Top Level Component State Down To Routes Using React-router?

- 1 answer

I've set up a React app using react-router that authenticates with Firebase.

I have this working but I now want to be able to manage the state of the logged in user (auth) from the main App Component, and have that pass down to the child Components instead of having to query the auth status in each Component.

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Link, browserHistory } from 'react-router';
import { createHistory } from 'history';

/*
  Firebase
*/
import Auth from './modules/Auth';
import Helpers from './modules/Helpers';

// Auth.unauth();
/*
  Import app modules
*/
import LogIn from './modules/LogIn';
import LogOut from './modules/LogOut';
import NotFound from './modules/NotFound';
import Register from './modules/Register';
import Dashboard from './modules/Dashboard';
import ResetPassword from './modules/ResetPassword';
import ChangePassword from './modules/ChangePassword';
import MyAccount from './modules/MyAccount';


const App = React.createClass({

  getInitialState: function() {
    return {
      loggedIn: Auth.getAuth(),
      userType: null
    }
  },

  setUserLevel: function(authData) {
    if(authData){
      Auth.child('users/'+authData.uid+'/user_level').once('value',
        (snapshot) => {
          this.setState({ userType: Helpers.getUserType(snapshot.val()) })
        },
        (error) => {
          this.setState({
            userType: Helpers.getUserType(0),
            error: error
          });
        }
      );
    }
  },

  updateAuth: function(loggedIn) {
    // console.log('Updating Auth');

    this.setUserLevel(loggedIn);

    this.setState({
      loggedIn: loggedIn
    })
  },

  componentWillMount: function() {
    Auth.onAuth(this.updateAuth);
  },

  render: function() {

    var regButton = (this.state.loggedIn) ? null : (<Link className="Navigation__link" to="register">Register</Link>);
    var accountButton = (this.state.loggedIn) ? (<Link className="Navigation__link" to="account">My Account</Link>) : null;

    return (
    <div>
      <nav className="Navigation">
        {this.state.loggedIn ? (
            <Link className="Navigation__link" to="/logout">Log out</Link>
          ) : (
            <Link className="Navigation__link" to="/login">Sign in</Link>
        )}
        {regButton}
        {accountButton}
        <Link className="Navigation__link" to="dashboard">Dashboard</Link>
      </nav>
      <div>
      {this.props.children}
      </div>
    </div>);
  }
})


function requireAuth(nextState, replaceState) {
  if (!Auth.getAuth())
    replaceState({ nextPathname: nextState.location.pathname }, '/login')
}

function notWhenLoggedIn(nextState, replaceState) {
  if (Auth.getAuth())
    replaceState({ nextPathname: nextState.location.pathname }, '/dashboard')
}

var routes = (
  <Router history={createHistory()}>
    <Route path="/" component={App}>
      <Route path="/login" component={LogIn} onEnter={notWhenLoggedIn} />
      <Route path="/logout" component={LogOut} onEnter={requireAuth} />
      <Route path="/register" component={Register} onEnter={notWhenLoggedIn} />
      <Route path="/resetpassword" component={ResetPassword} onEnter={notWhenLoggedIn} />
      <Route path="/change-password" component={ChangePassword} onEnter={requireAuth} />
      <Route path="/dashboard" component={Dashboard} onEnter={requireAuth} />
      <Route path="/account" component={MyAccount} onEnter={requireAuth} />
      <Route path="*" component={NotFound} />
    </Route>
  </Router>
)

ReactDOM.render(routes, document.querySelector('#main'));

I've now come stuck as I can't find a way to pass down the loggedIn state of the App component down as properties on the child components.

Is this even possible with react-router?

Ad

Answer

The best way I know how is to use the React.Children utility. Here's a quick Codepen to see the concept in action. http://codepen.io/anon/pen/RrKbjZ

So the code in the render() function of the App component in your example would look something like this.

render: function() {

  var regButton = (this.state.loggedIn) ? null : (<Link className="Navigation__link" to="register">Register</Link>);
  var accountButton = (this.state.loggedIn) ? (<Link className="Navigation__link" to="account">My Account</Link>) : null;
  var childrenWithProps = React.Children.map(this.props.children, (Child, i) => {
    return React.cloneElement(Child, {
      loggedIn: this.state.loggedIn
    });
  });

  return (
    <div>
      <nav className="Navigation">
        {
          this.state.loggedIn ? 
          (<Link className="Navigation__link" to="/logout">Log out</Link>) :
          (<Link className="Navigation__link" to="/login">Sign in</Link>)  
        }
        {regButton}
        {accountButton}
        <Link className="Navigation__link" to="dashboard">Dashboard</Link>
      </nav>
      <div>
        {childrenWithProps}
      </div>
    </div>
  );
}
Ad
source: stackoverflow.com
Ad