< Previous: Making Custom URLs with React Router Params   Next: Cleaning up Our Routes and Preparing for the Next Step >

Adding a Root Route Using React Router and IndexRoute

This topic is confusing for people with a similar accent to mine, because I pronounce "root" and "route" identically, whereas many other people pronounce "route" and "rout" identically.

Regardless, a root route (and not a root root) is a React Router path that sits at the very core of our app, and will be rendered no matter what path is reached. Well, technically it's a little more complicated than that, but just consider it one more step on Wittgenstein's Ladder.

We're going to create a root route as a way of giving our pages a common wrapper layout. React Router will render our root route first, which in turn is responsible for rendering its children.

Let's get started now: create a new file in the src/pages directory, and call it App.js. We'll use this to show some basic branding for our web app, then show all the content from our child page below. Please give the new file this content:

src/pages/App.js

import React from 'react';

class App extends React.Component {
    render() {
        return (
            <div>
                <h1>Unofficial GitHub Browser v0.1</h1>
                {this.props.children}
            </div>
        );
    }
}

export default App;

The only really interesting part of that code is {this.props.children}. All it means is "render my child components here," so all this new App component does is add a heading above whatever page component is rendered beneath it – i.e., our List and Detail components.

The page itself was simple enough, but now we need to update our index.js file, and this is a little trickier because you need to learn two new concepts:

  • Any route can have child routes inside it, and these build upon the parent route.
  • If you want a child route to be used as the default when no other child matches, you use a special route called <IndexRoute>.

Neither of those concepts will make any sense without a practical example, so I want to modify index.js immediately so you can see both of those new things in action. Here's the new code:

src/index.js

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

import App from './pages/App';
import List from './pages/List';
import Detail from './pages/Detail';

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

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

You'll see I've added an import for the new App component we created a few moments ago, but the main difference is this:

<Route path="/" component={ App }>
    <IndexRoute component={ List } />
    <Route path="detail/:repo" component={ Detail } />
</Route>

That's the React Router structure for those two new concepts I just introduced. First, notice the new <IndexRoute> component: it means "if my parent route was matched but none of my siblings matched, render me." Second, notice how <Route path="/" component={ App }> actually contains two things inside it: the IndexRoute and another Route.

Before continuing I want to explain both of these concepts in a bit more depth because they are both easy to get wrong. If you think you understand them, feel free to skip ahead.

The <IndexRoute> is important, and to explain what it does let's pretend it isn't there. If the user navigates to http://localhost:8080/, it would match the App route (path="/"), but it wouldn't match the Detail route (path="detail/:repo"), so all they would see is "Unofficial GitHub Browser v0.1" in large text.

If we wanted to use a regular <Route> in place of the <IndexRoute>, what path would it have? You might guess something like <Route path="" component={ List } />, but that just doesn't work. This is what <IndexRoute> gives us: a default child for the App component that will be used when no other route matches.

The second concept is having one route inside another, which is called a nested route. When you nest routes – i.e., put one route inside another - they build up as we saw earlier using this.props.children.

Right now we have a very simple structure: our App component at the base, then either List or Detail depending on what was matched. But it's possible to have routes inside routes inside routes inside… well, you get the point. If D is inside C, which is inside B, which is inside A, then all four components get rendered, adding their own parts to your user interface as needed.

It's because of this nesting that child routes don't need to start with a / in their path. If you look back to our routes list from 20 minutes ago we used <Route path="/detail/:repo" component={ Detail } /> but now we don't need to start the path with a / because we already matched that part of the URL in the parent route.

Now, for the sake of completeness I should add that you can start child route paths with a / if you want to, but it has a very special meaning: it lets you specify an exact URL for a child path (ignoring whatever its parents match) while keeping the nested component rendering. It's clever and I love that it's available, but at this stage in your React career I'd leave it out if I were you.

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: Making Custom URLs with React Router Params   Next: Cleaning up Our Routes and Preparing for the Next Step >

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