React Router


In this video we are going to configure a basic setup of React Router for our project, and also add in a 'Not Found' page, which will handle any situations where user's browse to routes that we don't explicitly handle.

We don't need to run any npm install commands at this stage, as we added the React Router as a dependency back in the first video in this section. However, if you haven't already done so, adding this module is as simple as:

npm install --save react-router@2.5.2

At this stage, if you haven't already done so, I would strongly advise you give the React Router README a once-over.

The interesting thing about React Router - in my opinion - is that it helps you visualise the nested structure of your application, simply by looking at the routing setup.

This differs to projects like Symfony, or Angular, where the routing definitions are line-by-line, often in any order you like, and often spread over many files. That's not to say you can't split your routing up in React Router, but from my experience, the end-result - even on larger projects - is a simpler overview of the projects routing structure.

If you haven't yet seen how React Router declares routes, I have shameless stolen the following from the official docs:

render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="about" component={About}/>
      <Route path="users" component={Users}>
        <Route path="/user/:userId" component={User}/>
      </Route>
      <Route path="*" component={NoMatch}/>
    </Route>
  </Router>
), document.getElementById('root'))

And here is how this project's routing structure will look, after the next few videos:

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={List}>
            <IndexRedirect to="/posts"/>
        </Route>
        <Route path="/posts/create" component={withRouter(Create)}/>
        <Route path="/posts/update/:postId" component={withRouter(Update)}/>
        <Route path="*" component={NotFoundPage}/>
      </Router>
    );
  }

I like this as it's really easy to see exactly what is going to be available - at a glance - in my project.

I'd hazard a guess that for the most part, this looks fairly intuitive. There are some less obvious things - {browserHistory}, and IndexRoute are likely the two that jump out. Potentially also the path="*" route, and maybe, why is the official version rendering onto an element, whereas ours is not?

Let me try and address each of these before we proceed.

Browser History / browserHistory

There is a page in the official docs dedicated to the implementation of history inside your application.

browserHistory is the recommended way of implementing your history, and gives human readable URLs - which is highly likely what you want.

There is also {hashHistory} which utilises a # character in your URL. This is a bit more like the way Angular projects seem to be, and I was more than happy to have an option to remove the hash character. Your milage may vary, of course.

Lastly, there is createMemoryHistory, which I haven't used, but is apparently useful for server-side rendering.

Again, for all three, I strongly advise at least a cursory glance at the docs.

Implementing Routing

The first step is to replace the current contents of our App.js file to handle our routing, which in turn will render out the correct component for any given route, rather than directly rendering out content from the App render function itself:

// /src/App.js

import React, { Component } from 'react';
import { Router, browserHistory, Route, IndexRoute, withRouter } from 'react-router'
import List from './containers/blogPosts/list';

export default class App extends Component {

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={List} />
      </Router>
    );
  }
}

At this stage our App should still be working. If you visit the same URL we have been using throughout, the List component (the table with our data from the previous video) should still be displaying.

The next step is to force a redirection of any users who hit the / route to a /posts route, which makes more sense for our users application journey. It also frees up the / route to be something more generic - a dashboard, or similar, which we aren't going to implement here.

Changing our route is super simple, literally just updating the path:

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/path" component={List} />
      </Router>
    );
  }

And of course, at this stage the / route is now broken.

React Router helpfully allows us to force a 'redirect' here, should a user visit the /, we can then force them on to some other route of our chosing. I'm going to do this using an IndexRedirect, but there may very well be other ways to do this:

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={List}>
            <IndexRedirect to="/posts"/>
        </Route>
      </Router>
    );
  }

And note how the nested IndexRedirect lives 'inside' the element that it redirects from? I really like that. The 'why' is obvious here, even if you don't fully understand the 'how'.

If you try this now, when you hit the / route, you should find you are redirect to /posts, and your browser URL is correctly updated. Nice.

Not Found Pages

But we have a problem.

What if the user browses to some route that doesn't exist - /bad-route-here - for example.

Well, with our current implementation, the user would end up seeing the shell / the site wrapper, including the header, the layout and look-and-feel. But no content. And worse, no error.

Fixing this is a simple, two-step process.

Firstly, we will define ourselves a catch-all route, and secondly, we will create a simple component - NotFoundPage - to display when the... page is not found :D

Defining the route is really easy:

import NotFoundPage from './components/NotFoundPage';

export default class App extends Component {

  render() {
    return (
      <Router history={browserHistory}>
        <Route path="/" component={List}>
            <IndexRedirect to="/posts"/>
        </Route>
        <Route path="*" component={NotFoundPage}/>
      </Router>
    );
  }
}

The routing configuration will be read from top to bottom, and having the last route be a * means it will match anything that hasn't yet been defined.

But the NotFoundPage doesn't yet exist. Oh the irony. Let's fix that:

// /src/components/NotFoundPage.js

import React, { Component } from 'react';

const NotFoundPage = () => {
    return (
        <h1>Nothing to see here, move along, move along...</h1>
    );
};

export default NotFoundPage;

Now, this is the end result, so be sure to watch the video from around the 3 minute mark to understand the iteration that goes into getting to this simple, single function.

From here we can continue adding routes and pointing them at our forthcoming components, which is exactly what we shall do in the next video.

More On React Router

If you'd like to dive a little deeper into the history, reasoning, and vision for the React Router project I'd strongly recommend this talk from React.js Conf 2015. It's well worth a watch.

Episodes