The Marketing Technologist.

We talk about analytics, code, data science and everything related to marketing technology. Backed by the tech geeks of Greenhouse Group.

React Router: a comprehensive introduction

When you need to route between different parts of your React application, you'll probably need a router. The most popular choice to do this in React is React Router. If you are familiar with routing in Ember, you'll get your routing up and running with React Router in no time.

In the following introduction to React Router I assume you already have a development environment up and running and you are familiar with React and CommonJS modules. It uses React 0.13.

Route components

There are 4 types of route components available with React Router:

  • Route
  • DefaultRoute
  • NotFoundRoute
  • RedirectRoute

We'll discuss all four in this post.

Setting up the basics

Let's install react-router using npm:

npm install react-router --save

Now we need to setup our page. We need an index.html file that references the (not yet existing) main.js and it should have an div element with an ID of app.

<html>
	<head>
    	<title>Our app</title>
    </head>
    <body>
    	<div id="app" />
        <script href="main.js"></script> 
    </body>
</html>

We place reference to main.js at the bottom of the body tag, just to be sure our DOM is ready for our app to bootstrap.

As for React components, we only need a simple high level container for our routes. Let's call it app.js. It only needs to contain some basic React boilerplate code. We use the ES6 syntax at Blue Mango, so it looks something like this:

var React = require('react');

export class ourApp extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>Welcome to our routed page</h1>
        <p>We hope you have great time.</p>
      </div>
    );
  }
}

We save this file at './components/app.js'. We'll create the referenced main.js file later.

Default route

Let's dive in directly. In most cases, the routes are defined in a global file called routes.js, and it exists in the root of our source folder.

First load the React Core and React Router:

var React = require('react');
var Router = require('react-router');

Next up, we'll need to define what should do when someone opens our page without adding any segments to the url. This is where the DefaultRoute comes in. We also need to load the Route component, because it forms the base of every other Route in our application.

var DefaultRoute = Router.DefaultRoute;
var Route = Router.Route;

Now that we have our core dependencies in place, we can start defining our routes. We define our routes in a nested, XML-like way, making it pretty declarative:

var routes = (
	<Route name="ourApp" path="/" handler={require('./components/ourApp.js')}>
   		<DefaultRoute handler={require('./components/home.js')} />
	</Route>
)

Let's see what is going on over here.

  1. We define a base route called ourApp. Because we've set the path to / it will match all our routes.
  2. In the handler attribute of this route, we define what component should be loaded when the route is matched.
  3. We nest every other route in our base route. The only required attribute for a route is the handler attribute.

You see I used require to define the handler. Of course we can also make a reference to the component and pass in that reference:

var Home = require('./components/home.js');
var routes = (
	<Route name="ourApp" path="/" handler={require('./components/ourApp.js')}>
   		<DefaultRoute handler={Home} />
	</Route>
)

We'll see how we can use this route in our components later on in this post.

A basic route

Ok. That one default route is not impressive, it is? We want some specific ones. Let's say we want to have the /contact path to head over to our Contact.js component. Easy, let's add a new Route component:

var routes = (
	<Route name="ourApp" path="/" handler={require('./components/ourApp.js')}>
    	<DefaultRoute handler={require('./components/home.js')} />
        <Route name="contact" handler={require(./componenents/contact.js)} />
	</Route>
)

Since we gave our Route's name attribute a value of contact, contact is now also the path of that route. If we want the name to differ from the path, you can explicitly define the path attribute. If you want the path to be contact-us, your code should look like this:

...
<Route name="contact" path="contact-us" handler={require(./componenents/contact.js)} />
...

Using RouteHandler

Now, how do we show the correct Route component in our application? Meet the RouteHandler component. The RouteHandler is the placeholder for your routed pages.

First, let's reference the RouteHandler in the component in which we want to show the routed pages. In our example this is component/app.js.

var RouteHandler = require('react-router').RouteHandler;

Now let's remove the hardcoded content and replace it with the RouteHandler component:

var React = require('react');
var RouteHandler = require('react-router').RouteHandler;

export class ourApp extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <RouteHandler />
    );
  }
}

Next up, we need to create a main.js that acts as an entry point for our application. This file bootstraps the router:

var React = require('react');
var Router = require('react-router');
var routes = require('./routes');

Router.run(routes, function(Handler) {
	React.render(<Handler />, document.getElementById('app');
});

When we run our indx.html, we should see our routes working.

Parameters

Not all routes are as static as our previous contact page example. Let's switch to a more complex, classic example of working with a collection of employees. What if we want to show the details of a specific employee by its employee ID?

<Route name="ourApp" path="/" handler={require('./components/app.js')}>
	<Route name="employeeDetail" path="/employees/:empoyeeId" handler={require('./components/empoyee.js')} />
</Route>

Now we can simply retrieve the passed employee ID using the props.params.employeeId property in our Employee component:

class Employee extends React.Component{
  constructor(props){
    super(props);
    // Here we have access to this.props.params.employeeId
  }
}

Link to a Route

React Router offers an abstraction over links called Link. Link allows you to leverage the routes you've defined, so you don't have to repeat them every time you want to link to a given page.

Let's say we want to link to an employee with an ID of 1. This page URL looks like /employees/1.

Remember we added a route for this page that has has a placeholder for the employee ID and the name employeeDetail:

<Route name="employeeDetail" path="/employees/:empoyeeId" handler={require('./components/empoyee.js')} />

Having defined a name gives us an easy hook for referencing this route in our links. To create a link that points to this route, we can write some JSX that uses the Link component.

<Link to="employeeDetail" params={{employeeId: 1}}>Go to employee #1</Link>

We've set the to property to employeeDetail, because that's the name of the route we want to use. params can be a JSON object of data that we can use in the URL. When this JSX is actually compiled the following normal anchor will be generated:

<a href="/employees/1">Go to employee #1</a>

The big win here is that it's common to create multiple links to any given resource in your app. The Link component effectively normalizes your links by abstracting the paths, and simply let's you reference your routes by name. Nice, huh?

'Page Not Found' Route

What about 404's? When someone enters a URL that doesn't exist, it would be nice if our application showed a friendly error page.

First, of course, let's create a page that you want to display as a 404. After this, let's go over to your routes file and define a 'not found' route. First we need to make a reference to the NotFoundRoute component that comes with React Router.

var NotFoundRoute = Router.NotFoundRoute;

Now you can use the NotFoundRoute component in your routes, just like the normal Route object. We don't have to define a name for it, but we do need to define a handler:

var routes = (
	<Route name="ourApp" path="/" handler={require('./components/ourApp.js')}>
    	.. your other routes ...
        <NotFoundRoute handler={require('./components/notFound.js')} />
	</Route>
)

This gives us an easy way to handle 404's using React Router. You can also use an aterisk (*) as a path to match all routes that are not defined in the routes configuration.

<Route path="*" handler={require('./components/notFound.js')} />

Make sure this is the last route in your route configuration.

Redirect Route

In the previous section, I discussed 404's. In case an old URL is replaced by a new one, it would be nice to redirect the user to the new page instead of showing a 404 on the old URL.

Luckily, this is very simple with React Router. To do this, we need to reference the Redirect component in your routes file:

var Redirect = Router.Redirect;

A redirect Route looks like this:

<Redirect from='old-path' to='new-path' />

You can include your query string parameters in the from attribute too, but this is not necessary because they will be passed to the new path anyway.

The from attribute can also include dynamic segments, so you can use an * to redirect segment that isn't found. The next example will redirect all pages under old-path to the new-path:

<Redirect from='old-path/*' to='new-path' />

Nested routes

As our applications grow, there will be a demand for nested routes. Let's say we want to have an images section in our employee detail view. We can simply nest this route in our existing employee route:

<Route name="ourApp" path="/" handler={require('./components/app.js')}>
	<Route name="employeeDetail" path="/employees/:empoyeeId" handler={require('./components/empoyee.js')}>
    	<Route name="employeeImages" path="images" handler={require('./components/empoyee_images.js')}>
    </Route>
</Route>

To make this work, we need to add an extra RouteHandler in employee.js where the nested images page will be rendered:

export class Employee extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div>
        <h1>Employee</h1>
        <RouteHandler />
      </div>
    );
  }
}

Now we should be able to visit /employees/1/images.

What about query string parameters?

You might want to pass some additional information through the query string. Easy!

<Route name="search" path="?query:query&limit=:limit&pageSize=:pageSize" handler={SearchDisplay}/>

Now you can easily get these values in your component using router.getCurrentQuery():

var { query, limit, pageSize} = this.context.router.getCurrentQuery();

You have to make sure that you included router in your contextTypes.

class MyComp extends React.Component{
  constructor(props, context){
    super(props);
    // here you have access to context.router.getCurrentQuery()
  }
}

MyComp.contextTypes = {
    router: React.PropTypes.func.isRequired
};

Conclusion

React Router is a complete routing solution for your React application. We discussed client-side routing in this article, but you can also use React Router in a server-side or shared environment (Universal Routing in React with ES6). You might also want to check the react-router increases your productivity video recordered at React Conf 2015. You can also implement routing without React Router, of course.

Note: After Facebook recently released React 0.14 we'll also need to update this post (see comments).