React-router: Compose onEnter and onLeave Route hooks using middlewares

Created on 7 Aug 2015  路  3Comments  路  Source: ReactTraining/react-router

In [email protected], I was looking for a way to compose things for a <Route>'s onEnter hook. For instance:

  • first check if user is logged in, if not redirect accordingly
  • if auth check passed, look at the search query, and if empty redirect to a default search query

I did it using the "middleware" pattern described below. My questions are:

  • does this make sense? or is there a better way?
  • if this makes sense, should it be part of react-router (would require an API change)? or should it just be a "recipe" in the examples directory?
// Used to compose Route `onEnter` and `onLeave` hooks.
// Middlewares look like:
//
// function middleware(next) {
//   return function handler(nextState, transition) {
//     if (condition) {
//       transition.to('/some/path');
//       return;
//     }
//     return next(nextState, transition);
//   }
// }
function applyMiddleware(...middlewares) {
  // Middleware handlers are all side-effect,
  // so if we reach the last we don't need to do anything
  var finish = _.noop;
  // Middlewares will be called from left to right
  // until one of them doesn't call `next(nextState, transition)`
  var handler = _.compose(...middlewares)(finish);

  return function(nextState, transition) {
    return handler(nextState, transition);
  };
}

// Auth middleware
function requireAuth(next) {
  return function(nextState, transition) {
    if (!auth.isLoggedIn()) {
      transition.to('/login', null, {redirect: nextState.location});
      return;
    }

    next(nextState, transition);
  };
}

// Default search query middleware
function ensureQuery(next) {
  function(nextState, transition) {
    if (_.isEmpty(nextState.location.query)) {
      transition.to('/search', defaultQuery);
      return;
    }

    next(nextState, transition);
  };
}

var router = (
  <Router history={history}>
    <Route component={App}>
      <Route path="login" component={Login}/>
      <Route path="account" component={Account} onEnter={applyMiddleware(requireAuth)}/>
      <Route path="search" component={Search} onEnter={applyMiddleware(requireAuth, ensureQuery)}/>
      <Redirect from="/" to="search" />
    </Route>
  </Router>
);
feature

Most helpful comment

You can actually do "middleware" with route hierarchy:

<Router>
  <Route onEnter={requireAuth}>
    <Route onEnter={addDefaultQuery>
      <Route path="/" component={App}/>
    <Route>
  </Route>
</Router>

Or build a middleware layer yourself, with whatever API you'd like and pass it into the top, a lot like redux reducers:

var middleware = compose(requireAuth, addDefaultQuery, ...etc);

<Router>
  <Route onEnter={middleware}>
    <Route path="/" component={App}/>
  </Route>
</Router>

All 3 comments

+1 was looking for the same thing

seems cool, I'll leave open for later consideration

You can actually do "middleware" with route hierarchy:

<Router>
  <Route onEnter={requireAuth}>
    <Route onEnter={addDefaultQuery>
      <Route path="/" component={App}/>
    <Route>
  </Route>
</Router>

Or build a middleware layer yourself, with whatever API you'd like and pass it into the top, a lot like redux reducers:

var middleware = compose(requireAuth, addDefaultQuery, ...etc);

<Router>
  <Route onEnter={middleware}>
    <Route path="/" component={App}/>
  </Route>
</Router>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

sarbbottam picture sarbbottam  路  3Comments

imWildCat picture imWildCat  路  3Comments

alexyaseen picture alexyaseen  路  3Comments

andrewpillar picture andrewpillar  路  3Comments

mr-e- picture mr-e-  路  3Comments