< Previous: Refactoring our State Code: Passing Parameters in onClick   Next: How to Add a New Route to React Router >

Introducing React Router

All the work we've done so far has either been in index.js or Detail.js, but now we're going to add a third file called List.js that will render a home page for our app. From there, users will be able to select a GitHub repository, and doing so will show Detail.js as before.

So, we'll go from having just one page listing all the React commits, forks and pulls on GitHub, to having a homepage first where users can select React, React Native, Jest, or other Facebook projects of our choosing. This involves a fairly big change, so we're going to do this in two parts: first implement React Router in a way that preserves the exact behavior we have right now, then second add the new home page.

If you were wondering, React Router is a component that loads different pages depending on the URL your user asked for. So if the user goes to http://localhost:8080/hello it would serve one page, and http://localhost:8080/world would serve a different page.

Well, that's not strictly true. To avoid having to add a server configuration, the pages React Router serves up will all start with /#/, e.g. http://localhost:8080/#/hello. This means that index.html will automatically be used to render all pages, which in turn means that React Router will be able to load the right page.

Your current index.js file should look like this:

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import Detail from './pages/Detail';

ReactDOM.render(
    <Detail message="This is coming from props!" />,
    document.getElementById('app')
);

I'd like you to add two more imports after the first two you have:

src/index.js

import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';

The first import brings four components, of which we'll be using two immediately and the other two shortly. Router is React Router itself, which takes a list of URLs and React components and puts the two together. Route is one individual URL mapping, e.g. the URL detail and our Detail component. IndexRoute is used as a default route; we'll get onto that soon. useRouterHistory is something I'll explain in just a moment.

The second import brings in a great deal of complexity, and chances are you don't want to know about it. In short, React Router needs a smart way to handle history. This is partly done through that # sign (known as a hash history because # is called "hash" to people who don't play music), and partly through special query keys in your URLs. These query keys aren't needed in our example and just make things look a bit ugly, so we'll be taking them out. But to take them out, we need this import line as you'll see in a moment.

With those new imports, it's time to turn to the main rendering of our app. Right now we just render a Detail component, but we need to change that so we use React Router instead. Here's how it looks in its most basic form:

src/index.js

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

So, rather than rendering our Detail component directly, we now render Router, which in turn creates the appropriate child component for the URL that gets matched. Right now we specify only one URL, /, and tell React Router to load our Detail component.

If you save those changes, try refreshing your browser. All being well, http://localhost:8080/ should update to become something like http://localhost:8080/#/?_k=7uv5b6. That's the hash history in action: that ?_k= part is a unique key used to track state between locations, but we really don't need it so we'll remove it.

A basic install of React Router shows URLs with random numbers in the query string.

At the same time, we're also going to add what might seem like a bit of a hack, but I'm afraid it's a required hack until React Router solves it permanently. You see, when you navigate from /#/someurl to /#/someotherurl, you're not actually moving anywhere – React Router just unloads the previous components and loads the new one in its place. This causes a problem with scrolling, because if the user had scrolled half way down the page before changing URLs, they would remain half way scrolled down the page for the new component.

So, the hack is this: whenever the React Router updates, we tell the browser to scroll back to the top of the document, just as it would if we were changing pages the old-fashioned way.

We can make both these changes at the same time. Replace your current ReactDOM.render() lines with this one:

src/index.js

const appHistory = useRouterHistory(createHashHistory)({ queryKey: false })

ReactDOM.render(
    <Router history={appHistory} onUpdate={() => window.scrollTo(0, 0)}>
        <Route path="/" component={ Detail } />
    </Router>,
    document.getElementById('app')
);

It's not particularly graceful code, but it is important. The history part is what removes the ?_k= mess from our URLs, because we're using the "hash history" system with its query code setting turned off. The onUpdate part is what makes sure the user's scroll position resets when they move between components.

With those changes saved, you should be able to navigate to http://localhost:8080/ and find yourself on http://localhost:8080/#/, which is what we want. Again, removing the # sign requires server configuration that we aren't going to do here. If you'd like to read more about this, here's the React Router documentation page you're looking for.

Buy the book for $10

Get the complete, unabridged Hacking with React e-book and take your learning to the next level - includes a 45-day no questions asked money back guarantee!

If this was helpful, please take a moment to tell others about Hacking with React by tweeting about it!

< Previous: Refactoring our State Code: Passing Parameters in onClick   Next: How to Add a New Route to React Router >

Copyright ©2016 Paul Hudson. Follow me: @twostraws.