Leveling Up With React: React Router
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, stay tuned to the screencasts area, as we have a pairing screencast to get you started coming later this week. This series picks up where the basics leave off.
Article Series
Part 1: React Router (You are here!)
Part 2: Container Components (Coming soon!)
Part 3: Redux (Coming soon!)
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:
- React.js and How Does It Fit In With Everything Else?
- Rethinking (Industry) Best Practices
- React.js Introduction For People Who Know Just Enough jQuery To Get By
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 and
are two different things. They are technically React components, but they don’t actually create DOM themselves. While it may look like the
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 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 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:
When you start to think about how this mockup can be broken down into re-usable sections, you might end up with this idea:
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 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:
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 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: or two:
...
. This is true of all JSX including your custom components and normal DOM nodes. For instance,
when rendered.
For brevity, just imagine WidgetList
resembles the UserList
.
Since has two child routes now, the user can visit
/users
or /widgets
and the corresponding 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 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, will have a
component
attribute with no path
, as in the SearchLayout
route from above. Other times, it might be necessary to have a 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 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
not
When creating anchors for your routes, you’ll need to use instead of
. Don’t worry though, when using the
component, React Router will ultimately give you an ordinary anchor in the DOM. Using
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 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 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 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 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:

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:
is deprecated. Use
instead.
is deprecated. Use
instead.
is deprecated. See Alternative
is deprecated.
willTransitionTo
is deprecated. See onEnterwillTransitionFrom
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
Part 1: React Router (You are here!)
Part 2: Container Components (Coming soon!)
Part 3: Redux (Coming soon!)
Leveling Up With React: React Router is a post from CSS-Tricks