Authentication Container with React/react-router


#1

Does anyone have any tips on building an authentication container that can wrap a react-meteor-data container? Or, just how to wrap one container with another with react-router.


#2

I just posted something that might help over here:

Additionally, here’s my (in progress) router.js. I’m using bootstrap and the associated bootstrap-accounts-ui to handle the actual login/logout right now.

import React from 'react';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';

import AppContainer from '../containers/AppContainer.jsx';

import EventsTable from '../components/EventsTable.jsx';
import Ledger from '../components/Ledger.jsx';
import { NotFound } from '../components/NotFound.jsx';

export default (
  <Router history={browserHistory}>
    <Route path="/" component={ AppContainer }>
      <IndexRoute component={ EventsTable } />
    </Route>
    <Route path="*" component={ NotFound }>
    </Route>
  </Router>
);

Cheers


#3

I did some more work on this after I realized that my ‘logged out’ state bypassed the router, leaving me with no route handling other than /. So, I tied the Router instantiation to the Meteor.user results, using separate route files for the loggedin/loggedout states. The react router generally refuses to ‘hot’ reload once it has been instantiated, but I found a technique to force a new instance (and therefore be able to use a completely different routes file whenever I want) by passing a unique key prop to the router instantiation like this:

uniqueKey = Date.now()
<Provider store={Store}>
  <Router key={uniqueKey} history={browserHistory}>
    {whateverRoutesIWant}
  </Router>
</Provider>

So I moved the logic that waits for meteor.user() to come back valid to a parent container for my app, and this is also where my router and redux instantiation happens. I haven’t noticed any negative side effects yet.

So my component chain now looks like this:

  client/main.jsx       (hooks the top-level React component to the DOM)
          |
          |
App_SuperContainer.jsx  (meteor data container that provides Meteor.user() 
          |              results as a prop that reactively updates)
          |
          |
   App_Super.jsx        (Instantiates the redux store and react-router 
                         instance based on logic that uses Meteor.user() prop)
          |
          |
       App.jsx          (contains the main presentation component and
                         renders children supplied by react-router)
          |
          |
Navbar.jsx, body components

The only time App_Super is rerendered, triggering a reinstiation of the Router, is when a user signs in, signs out, or when a manual page reload occurs. I can post complete source files if anyone is interested.

-Jerimiah


#4

I’d definitely be interested in seeing what you did. I have a very similar requirement where nearly all of my routes are entirely dependent on if the user is signed in or not and currently the only way I can handle it is putting routing logic in each component. I get the basic idea of what you did and kind of have it working, but fitting in the redux piece with it is where I am struggling. Thanks!


#5

Hi! Do you can post full source of your solution? I can’t reproduce this mechanics:

Warning: [react-router] You cannot change <Router routes>; it will be ignored


#6

chef does this in his base repo:


#7

Here’s the source files for how I did it. For the record, I’m not a professional meteor developer and have never deployed a meteor app to a production environment. I’m just playing around. This is probably a total hack and it may not even work for more than one user at a time. I honestly haven’t tested that. Proceed with caution. :slight_smile:

client/main.jsx

import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { Provider } from 'react-redux';

import '../imports/startup/accounts-config.js';
import App_SuperContainer from '../imports/client/containers/App_SuperContainer.jsx';

function AppRoot() {
  return (
    <App_SuperContainer/>
  )
}

Meteor.startup(() => {
  render(<AppRoot />, document.getElementById('render-target'));
});

App_SuperContainer.jsx

import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import AppSuper from '../components/App_Super.jsx';

export default createContainer(({ params }) => {
  const currentUser = Meteor.user();

  return {
currentUser,
  };
}, AppSuper);

App_Super.jsx

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Store from '../store/store.js';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import authRoutes from '../router/authRouter.jsx';
import Routes from '../router/router.jsx';
import { Events } from '../../api/events.js';
import { Meteor } from 'meteor/meteor';

export default class AppSuper extends Component {
  render(){
    //console.log("this.props.currentUser: "+this.props.currentUser);
    console.log("Rendering the SuperContainer")
    var userDataAvailable = true
    var currentUser = this.props.currentUser
    if (currentUser === undefined) {
      userDataAvailable = false
    }

    var loggedOut = (!currentUser && userDataAvailable);
    var loggedIn = (currentUser && userDataAvailable);

    if (loggedIn) {
      console.log("Logged in. Refreshing the router...")
      console.log(currentUser)

      let cachebuster = Date.now()
      return(
        <Provider store={Store}>
          <Router key={cachebuster} history={browserHistory}>
            {authRoutes}
          </Router>
        </Provider>
      )
    }

    if (loggedOut) {
      console.log("Logged out. Refreshing the router...")
      let cachebuster = Date.now()
      return(
        <Provider store={Store}>
          <Router key={cachebuster} history={browserHistory}>
            {Routes}
          </Router>
        </Provider>
      )
    }

    console.log("Still waiting for usable userdata")
    return(
      <div></div>
    )
  }
}

App.jsx

import React, { Component } from 'react';
import { Meteor } from 'meteor/meteor';

export default class App extends Component {
  render() {
    //console.log(this.props)
    return (
      <div>
        {this.props.nav}

        <div>
          <div className="col-fixed">
            {this.props.sidebar}
          </div>
          <div className="row">
            <div className="col-md-12">
              {this.props.content}
            </div>

          </div>
        </div>

      </div>
    );
  }
}

router.jsx (logged out)

import React, {Component} from 'react';
import { Router, Route, Redirect, browserHistory, IndexRoute } from 'react-router';

import App from '../components/App.jsx';
import Hero from '../components/Hero.jsx';

export default (
    <Route path="/" component={ App }>
      <IndexRoute loggedIn={false} components={{ nav: null, content: Hero }}></IndexRoute>
      <Redirect from="*" to="/" />
    </Route>
);

authRouter.jsx (logged in)

import React, {Component} from 'react';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';

import App from '../components/App.jsx';
import EventsTable from '../components/EventsTable.jsx';
import Navbar from '../components/Navbar.jsx';
import Sidebar from '../components/Sidebar.jsx';
import Ledger from '../components/Ledger.jsx';
import About from '../components/About.jsx';
import Hero from '../components/Hero.jsx';
import NotFound from '../components/NotFound.jsx';

export default (
    <Route loggedIn={true} component={ App }>
      <Route path="/"      components={{ nav: Navbar, content: Hero }} />
      <Route path="about"  components={{ nav: Navbar, sidebar: Sidebar, content: About }} />
      <Route path="events" components={{ nav: Navbar, sidebar: Sidebar, content: EventsTable }} />
      <Route path="ledger" components={{ nav: Navbar, sidebar: Sidebar, content: Ledger }} />
    </Route>
);