Leveling up with React: React Router

Avatar of Brad Westfall
Brad Westfall on (Updated on )

This tutorial is the first of a three-part series on React by Brad Westfall. When Brad pitched me this, he pointed out there are a good amount of tutorials on getting started in React, but not as much about where to go from there. If you’re brand new to React, I recommend watching this intro video. This series picks up where the basics leave off.

Article Series:

  1. React Router (You are here!)
  2. Container Components
  3. Redux

Warning! This article was written pre-React Router 4, which has become a more standard choice for routing in React. There is a new article covering React Router 4 here you should definitely read.

When I was first learning, I found lots of beginner guides (i.e. 1, 2, 3, 4) that showed how to make single components and render them to the DOM. They did a fine job of teaching the basics like JSX and props, but I struggled with figuring out how React works in the bigger picture – like a real-world Single Page Application (SPA). Since this series covers a lot of material, it will not cover the absolute beginner concepts. Instead, it will start with the assumption that you already understand how to create and render at least one component.

For what it’s worth, here are some other great guides that aim at beginners:

Series Code

This series also comes with some code to play with at GitHub. Throughout the series, we’ll be building a basic SPA focused around users and widgets.

To keep things simple and brief, the examples in this series will start by assuming that React and React Router are retrieved from a CDN. So you won’t see require() or import in the immediate examples below. Towards the end of this tutorial though, we’ll introduce Webpack and Babel for the GitHub guides. At that point, it’s all ES6!

React-Router

React isn’t a framework, it’s a library. Therefore, it doesn’t solve all an application’s needs. It does a great job at creating components and providing a system for managing state, but creating a more complex SPA will require a supporting cast. The first that we’ll look at is React Router.

If you’ve used any front-end router before, many of these concepts will be familiar. But unlike any other router I’ve used before, React Router uses JSX, which might look a little strange at first.

As a primer, this is what it’s like to render a single component:

var Home = React.createClass({
  render: function() {
    return (<h1>Welcome to the Home Page</h1>);
  }
});

ReactDOM.render((
  <Home />
), document.getElementById('root'));

Here’s how the Home component would be rendered with React Router:

...

ReactDOM.render((
  <Router>
    <Route path="/" component={Home} />
  </Router>
), document.getElementById('root'));

Note that <Router> and <Route> are two different things. They are technically React components, but they don’t actually create DOM themselves. While it may look like the <Router> itself is being rendered to the 'root', we’re actually just defining rules about how our application works. Moving forward, you’ll see this concept often: components sometimes exist not to create DOM themselves, but to coordinate other components that do.

In the example, the <Route> defines a rule where visiting the home page / will render the Home component into the 'root'.

Multiple Routes

In the previous example, the single route is very simple. It doesn’t give us much value since we already had the ability to render the Home component without the router being involved.

React Router’s power comes in when we use multiple routes to define which component should render based on which path is currently active:

ReactDOM.render((
  <Router>
    <Route path="/" component={Home} />
    <Route path="/users" component={Users} />
    <Route path="/widgets" component={Widgets} />
  </Router>
), document.getElementById('root'));

Each <Route> will render its respective component when its path matches the URL. Only one of these three components will be rendered into the 'root' at any given time. With this strategy, we mount the router to the DOM 'root' once, then the router swap components in and out with route changes.

It’s also worth noting that the router will switch routes without making requests to the server, so imagine that each component could be a whole new page.

Re-usable Layout

We’re starting to see the humble beginnings of a Single Page Application. However, it still doesn’t solve real-world problems. Sure, we could build the three components to be full HTML pages, but what about code re-use? Chances are, these three components share common assets like a header and sidebar, so how do we prevent HTML repetition in each component?

Let’s imagine we were building a web app that resembled this mockup:

A simple website mockup.

When you start to think about how this mockup can be broken down into re-usable sections, you might end up with this idea:

How you might break up the simple web mockup into sections.

Thinking in terms of nestable components and layouts will allow us to create reusable parts.

Suddenly, the art department lets you know that the app needs a page for searching widgets which resembles the page for searching users. With User List and Widget List both requiring the same “look” for their search page, the idea to have Search Layout as a separate component makes even more sense now:

Search for widgets now, in place of users, but the parent sections remain the same.

Search Layout can be a parent template for all kinds of search pages now. And while some pages might need Search Layout, others can directly use Main Layout without it:

A layout decoupled.

This is a common strategy and if you’ve used any templating system, you’ve probably done something very similar. Now let’s work on the HTML. To start, we’ll do static HTML without considering JavaScript:

<div id="root">

  <!-- Main Layout -->
  <div class="app">
    <header class="primary-header"><header>
    <aside class="primary-aside"></aside>
    <main>

      <!-- Search Layout -->
      <div class="search">
        <header class="search-header"></header>
        <div class="results">

          <!-- User List -->
          <ul class="user-list">
            <li>Dan</li>
            <li>Ryan</li>
            <li>Michael</li>
          </ul>

        </div>
        <div class="search-footer pagination"></div>
      </div>

    </main>
  </div>

</div>

Remember, the 'root' element will always be present since it’s the only element that the initial HTML Body has before JavaScript starts. The word “root” is appropriate because our entire React application will mount to it. But there’s no “right name” or convention to what you call it. I’ve chosen “root”, so we’ll continue to use it throughout the examples. Just beware that mounting directly to the <body> element is highly discouraged.

After creating the static HTML, convert it into React components:

var MainLayout = React.createClass({
  render: function() {
    // Note the `className` rather than `class`
    // `class` is a reserved word in JavaScript, so JSX uses `className`
    // Ultimately, it will render with a `class` in the DOM
    return (
      <div className="app">
        <header className="primary-header"><header>
        <aside className="primary-aside"></aside>
        <main>
          {this.props.children}
        </main>
      </div>
    );
  }
});

var SearchLayout = React.createClass({
  render: function() {
    return (
      <div className="search">
        <header className="search-header"></header>
        <div className="results">
          {this.props.children}
        </div>
        <div className="search-footer pagination"></div>
      </div>
    );
  }
});

var UserList = React.createClass({
  render: function() {
    return (
      <ul className="user-list">
        <li>Dan</li>
        <li>Ryan</li>
        <li>Michael</li>
      </ul>
    );
  }
});

Don’t get too distracted between what I’m calling “Layout” vs “Component”. All three of these are React components. I just choose to call two of them “Layouts” since that’s the role they’re performing.

We will eventually use “nested routes” to place UserList inside SearchLayout, then inside MainLayout. But first, notice that when UserList is placed inside its parent SearchLayout, the parent will use this.props.children to determine its location. All components have this.props.children as a prop, but it’s only when components are nested that the parent component gets this prop filled automatically by React. For components that aren’t parent components, this.props.children will be null.

Nested Routes

So how do we get these components to nest? The router does it for us when we nest routes:

ReactDOM.render((
  <Router>
    <Route component={MainLayout}>
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

Components will be nested in accordance with how the router nests its routes. When the user visits the /users route, React Router will place the UserList component inside SearchLayout and then both inside MainLayout. The end result of visiting /users will be the three nested components placed inside 'root'.

Notice that we don’t have a rule for when the user visits the home page path (/) or wants to search widgets. Those were left out for simplicity, but let’s put them in with the new router:

ReactDOM.render((
  <Router>
    <Route component={MainLayout}>
      <Route path="/" component={Home} />
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
        <Route path="widgets" component={WidgetList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

You’ve probably noticed by now that JSX follows XML rules in the sense that the Route component can either be written as one tag: <Route /> or two: <Route>...</Route>. This is true of all JSX including your custom components and normal DOM nodes. For instance, <div /> is valid JSX and will convert to <div></div> when rendered.

For brevity, just imagine WidgetList resembles the UserList.

Since <Route component={SearchLayout}> has two child routes now, the user can visit /users or /widgets and the corresponding <Route> will load its respective components inside the SearchLayout component.

Also, notice how the Home component will be placed directly inside MainLayout without SearchLayout being involved — because of how the <Route>s are nested. You can probably imagine it’s easy to rearrange how layouts and components are nested by rearranging the routes.

IndexRoutes

React Router is very expressive and often there’s more than one way to do the same thing. For example we could also have written the above router like this:

ReactDOM.render((
  <Router>
    <Route path="/" component={MainLayout}>
      <IndexRoute component={Home} />
      <Route component={SearchLayout}>
        <Route path="users" component={UserList} />
        <Route path="widgets" component={WidgetList} />
      </Route> 
    </Route>
  </Router>
), document.getElementById('root'));

Despite its different look, they both work the exact same way.

Optional Route Attributes

Sometimes, <Route> will have a component attribute with no path, as in the SearchLayout route from above. Other times, it might be necessary to have a <Route> with a path and no component. To see why, let’s start with this example:

<Route path="product/settings" component={ProductSettings} />
<Route path="product/inventory" component={ProductInventory} />
<Route path="product/orders" component={ProductOrders} />

The /product portion of the path is repetitive. We can remove the repetition by wrapping all three routes in a new <Route>:

<Route path="product">
  <Route path="settings" component={ProductSettings} />
  <Route path="inventory" component={ProductInventory} />
  <Route path="orders" component={ProductOrders} />
</Route>

Again, React Router shows its expressiveness. Quiz: did you notice the issue with both solutions? At the moment we have no rules for when the user visits the /product path.

To fix this, we can add an IndexRoute:

<Route path="product">
  <IndexRoute component={ProductProfile} />
  <Route path="settings" component={ProductSettings} />
  <Route path="inventory" component={ProductInventory} />
  <Route path="orders" component={ProductOrders} />
</Route>

Use <Link> not <a>

When creating anchors for your routes, you’ll need to use <Link to=""> instead of <a href="">. Don’t worry though, when using the <Link> component, React Router will ultimately give you an ordinary anchor in the DOM. Using <Link> though is necessary for React Router to do some of its routing magic.

Let’s add some link (anchors) to our MainLayout:

var MainLayout = React.createClass({
  render: function() {
    return (
      <div className="app">
        <header className="primary-header"></header>
        <aside className="primary-aside">
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/users">Users</Link></li>
            <li><Link to="/widgets">Widgets</Link></li>
          </ul>
        </aside>
        <main>
          {this.props.children}
        </main>
      </div>
    );
  }
});

Attributes on <Link> components will be passed through to the anchor they create. So this JSX:

<Link to="/users" className="users">

Will become this in DOM:

<a href="/users" class="users">

If you need to create an anchor for non-router-paths, such as an outside website, then use normal anchor tags as usual. For more information, see the documentation for IndexRoute and Link.

Active Links

A cool feature of the <Link> component is its ability to know when it’s active:

<Link to="/users" activeClassName="active">Users</Link>

If the user is on the /users path, the router will seek out matching anchors that were made with <Link> and it will toggle their active class. See more on this feature.

Browser History

To prevent confusion, I’ve left out an important detail until now. The <Router> needs to know which history tracking strategy to use. React Router docs recommend browserHistory which is implemented as follows:

var browserHistory = ReactRouter.browserHistory;

ReactDOM.render((
  <Router history={browserHistory}>
    ...
  </Router>
), document.getElementById('root'));

In previous versions of React Router, the history attribute was not required and the default was to use hashHistory. As the name suggests, it used a # hash sign in the URL to manage front-end SPA-style routing, similar to what you might expect from a Backbone.js router.

With hashHistory, URLs will look like this:

  • example.com
  • example.com/#/users?_k=ckuvup
  • example.com/#/widgets?_k=ckuvup

What’s up with those ugly query strings though?

When browserHistory is implemented, the paths look more organic:

  • example.com
  • example.com/users
  • example.com/widgets

There’s a caveat though on the server when browserHistory is used on the front-end. If the user starts their visit at example.com and then navigates to /users and /widgets, React Router handles this scenario as expected. However, if the user starts their visit by typing example.com/widgets directly into the browser, or if they refresh on example.com/widgets, then the browser must make at least one request to the server for /widgets. If there isn’t a server-side router though, this will deliver a 404:

Careful with URLs. You’ll need a server side router.

To solve the 404 problem from the server, React Router recommends a wildcard router on the server-side. With this strategy, no matter what server-side route is called, the server should always serve the same HTML file. Then if the user starts directly at example.com/widgets, even though the same HTML file is returned, React Router is smart enough to load the correct component.

The user won’t notice anything weird, but you might have concerns about always serving the same HTML file. In code examples, this series will continue to use the “wildcard router” strategy, but it’s up to you to handle your server-side routing in ways that you see fit.

Can React Router be used on both server-side and client-side in an isomorphic way? Sure it can, but that’s way beyond the scope of this tutorial.

Redirect with browserHistory

The browserHistory object is a singleton so you can include it in any of your files. If you need to manually redirect the user in any of your code, you can use it’s push method to do so:

browserHistory.push('/some/path');

Route Matching

React router handles route matching similarly to other routers:

<Route path="users/:userId" component={UserProfile} />

This route will match when the user visits any path that starts with users/ and has any value afterwards. It will match /users/1, /users/143, or even /users/abc (which you’ll need to validate on your own).

React Router will pass the value for :userId as a prop to the UserProfile. This props is accessed as this.props.params.userId inside UserProfile.

Router Demo

At this point, we have enough code to show a demo.

See the Pen React-Router Demo by Brad Westfall (@bradwestfall) on CodePen.

If you clicked on a few routes in the example, you might notice that the browser’s back and forward buttons work with the router. This is one of the main reasons these history strategies exist. Also, keep in mind that with each route you visit, there are no requests being made to the server except the very first one to get the initial HTML. How cool is that?

ES6

In our CodePen example, React, ReactDOM, and ReactRouter are global variables from a CDN. Inside the ReactRouter object are all kinds of things we need like the Router and Route components. So we could use ReactRouter like this:

ReactDOM.render((
  <ReactRouter.Router>
    <ReactRouter.Route ... />
  </ReactRouter.Router>
), document.getElementById('root'));

Here, we have to prefix all of our router components with their parent object ReactRouter. Or we could use ES6’s new destructuring syntax like this:

var { Router, Route, IndexRoute, Link } = ReactRouter

This “extracts” parts of ReactRouter into normal variables so we can access them directly.

Starting now, the examples in this series will use a variety of ES6 syntaxes including destructuring, the spread operator, imports/exports, and perhaps others. There will be a brief explanation of each new syntax as they appear and the GitHub repo that comes with this series also has lots of ES6 explanations.

Bundling with webpack and Babel

As stated before, this series comes with a GitHub repo so you can experiment with code. Since it will resemble the makings of a real-world SPA, it will use tools like webpack and Babel.

  • webpack bundles multiple JavaScript files into one file for the browser.
  • Babel will convert ES6 (ES2015) code to ES5, since most browsers don’t yet understand all of ES6. As this article ages, browsers will support ES6 and Babel may not be needed.

If you’re not too comfortable using these tools yet, don’t worry, the example code has everything setup already so you can focus on React. But be sure to review the example code’s README.md file for additional workflow documentation.

Be Careful About Deprecated Syntax

Searching Google for info on React Router might land you on one of the many articles or StackOverflow pages that were written when React Router was in its pre-1.0 release. Many features from the pre-1.0 release are deprecated now. Here’s a short list:

  • <Route name="" /> is deprecated. Use <Route path="" /> instead.
  • <Route handler="" /> is deprecated. Use <Route component="" /> instead.
  • <NotFoundRoute /> is deprecated. See Alternative
  • <RouteHandler /> is deprecated.
  • willTransitionTo is deprecated. See onEnter
  • willTransitionFrom is deprecated. See onLeave
  • “Locations” are now called “histories”.

See the full list for 1.0.0 and 2.0.0

Summary

There are still more features in React Router that weren’t shown, so be sure to check out the API Docs. The creators of React Router have also created a step-by-step tutorial for React Router and also checkout this React.js Conf Video on how React Router was created.

Special thanks to Lynn Fisher for the artwork @lynnandtonic


Article Series:

  1. React Router (You are here!)
  2. Container Components
  3. Redux