An Introduction to React-Router

I’ve been working on my pet project for the last few weeks – mostly getting the tooling converted to Webpack. I’ve also got my basic ExpressJS web service working and handled social authentication. I’m now at the stage where I have to handle multiple views within the single page application. My application is built around React and Flux. So how does one actually do multi-view applications in React? The answer is react-router and this is my introduction on how to do it.

Step 1: Install React-Router

This is fairly simple because react-router is an NPM package:

npm install --save-dev react-router

I’m placing this in the devDependencies because it is packaged up with Webpack, so I don’t need it when the server is actually running.

Step 2: Create some Pages

Pages are just React Components. So this is just like saying “create a couple of react components that show off the page content”. I don’t include the chrome in this – things like the header and footer. I only include the stuff that changes within each page.

There is one specific change and it deals with links between pages. Let’s say I have two pages – I’ll call it them home.jsx and mypage.jsx – they are pretty much a copy of each other. Here is home.jsx:

import React from 'react';
import { Link } from 'react-router';

/**
 * Displays the home page
 *
 * @extends React.Component
 */
export default class HomePage extends React.Component {
    /**
     * Render the component
     *
     * @returns {JSX.Element} the JSX Rendering of this component
     * @overrides React.Component#render
     */
    render() {
        return (
            <div className="page--home">
                <h1>{'Home'}</h1>
                <Link to="/mypage">{'My Page'}</Link>
            </div>
        );
    }
}

You must use “ When you are linking to another page within your application. In this case, I’m linking to the /mypage route, which I will configure later. The mypage.jsx is pretty much a copy of this:

import React from 'react';
import { Link } from 'react-router';

/**
 * Displays the home page
 *
 * @extends React.Component
 */
export default class HomePage extends React.Component {
    /**
     * Render the component
     *
     * @returns {JSX.Element} the JSX Rendering of this component
     * @overrides React.Component#render
     */
    render() {
        return (
            <div className="page--mypage">
                <h1>{'My Page'}</h1>
                <Link to="/">{'Home'}</Link>
            </div>
        );
    }
}

All I’ve done is to change the link to parameter to point to my “other” route. Great Big Takeaway here is to use Link from the react-router package to handle inter-page links. You can still use normal HTML links to link to things outside your application.

Step 3: Set up a Wrapper Page

Inevitably, you are going to want to wrap your pages inside of some chrome – some extra markup that is constant between pages. This includes things like the header and footer. I’ve created a Page.jsx for this purpose. I’ve got this linked to a flux store – the existance of which isn’t really important to the code, so I’ve removed it. You can check out the full version of Page.jsx on my GitHub repository:

import React from 'react';
import Footer from './Footer.jsx';
import Header from './Header.jsx';

/**
 * @extends React.Component
 */
export default class Page extends React.Component {
    static propTypes = {
        children: React.PropTypes.node
    };

    /**
     * Render the component
     *
     * @returns {JSX.Element} the JSX Rendering of this component
     * @overrides React.Component#render
     */
    render() {
        let jsx = (
            <div className="page">
                <div><Header/></div>
                <div className="page--grow">{this.props.children}</div>
                <div><Footer/></div>
            </div>
        );
        return jsx;
    }
}

The main thing to note here is that I include the this.props.children in the resulting JSX – this is important because this.props.children will hold our page definition.

Step 4: Update your initial React Render

My app.jsx is the entry point to the application. It used to just load the Page component and let that render things. I’ve changed this somewhat:

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

// Components
import Page from './components/Page.jsx';

// Pages
import HomePage from './pages/home.jsx';
import MyPage from './pages/mypage.jsx';
import InvalidPage from './pages/invalid.jsx';

require('./app.scss');

// Render the application
ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/" component={Page}>
            <IndexRoute component={HomePage}/>
            <Route path="mypage" component={MyPage}/>
        </Route>
        <Route path="*" component={InvalidPage}/>
    </Router>,
    document.getElementById('jsx-page')
);

There are several pieces here. The Router object is the outside component for actually doing the routing. The IndexRoute and Route elements define the routing map. In the application rendering, these objects are used to set up the routing matrix.

With this definition, the initial page will be . When the mypage route is active, will be rendered. The Page.jsx is our wrapping component that renders the chrome and then our page is rendered.

I’ve also got an invalid route – if the user goes to some URI that isn’t defined, then InvalidPage will be rendered. Note that this won’t have the Chrome because it is outside the “/” path.

Note that browser history is handled for you by including the history parameter in the Router component. This abstracts all the browser history management away for you. It just works.

Step 5: Handle parameterized routes

The above is all you need for a basic routing scheme. If you just want to link between the various pages in your application, you don’t need to go further. I’ve got an additional thing to do though. Let’s say I have a page called /spells that I want to render. I want to render two additional routes:

  • /spells points to a spell list
  • /spells/id brings up details about a specific spell

I can add the /spells easily – I just create a SpellsPage component and then register a /spells route. What about the other route? Let’s take a look at my adjusted app.jsx file:

ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/" component={Page}>
            <IndexRoute component={HomePage}/>
            <Route path="spells" component={SpellsPage}>
                <Route path="/spell/:id" component={SpellPage}/>
            </Route>
        </Route>
        <Route path="*" component={InvalidPage}/>
    </Router>,
    document.getElementById('jsx-page')
);

When I route to /spell/something, this route configuration will render “. I can now use this.props.children in the SpellsPage component and use this.props.params.id in the SpellPage. If I wanted to bring up a completely different page, I could, instead, do this:

ReactDOM.render(
    <Router history={browserHistory}>
        <Route path="/" component={Page}>
            <IndexRoute component={HomePage}/>
            <Route path="/spells" component={SpellsPage}/>
            <Route path="/spell/:id" component={SpellPage}/>
        </Route>
        <Route path="*" component={InvalidPage}/>
    </Router>,
    document.getElementById('jsx-page')
);

Now the rendering will be “ – the SpellsPage isn’t in the mix. This affords a lot of flexibility. Take a look at the spell.jsx file that implements the component:

import React from 'react';
import { Link } from 'react-router';

/**
 * Displays the home page
 *
 * @extends React.Component
 */
export default class SpellsPage extends React.Component {
    static propTypes = {
        params: React.PropTypes.shape({
            id: React.PropTypes.string.isRequired
        })
    };

    /**
     * Render the component
     *
     * @returns {JSX.Element} the JSX Rendering of this component
     * @overrides React.Component#render
     */
    render() {
        return (
            <div className="page--spell">
                <h1>{`Spell Page for ID ${this.props.params.id}`}</h1>
                <Link to="/spells">{'Spells'}</Link>
                <Link to="/">{'Home'}</Link>
            </div>
        );
    }
}

This is probably enough to cover most common scenarios. However, react-router is also flexible enough to cover the more complex scenarios, like dynamic routing and hooks (for example, to prevent navigation if you haven’t saved).

One thing to be aware of is if you use Flux stores. You need to ensure you only register with the store during the componentWillMount() lifecycle method and deregister from the store during the componentWillUnmount() lifecycle method. Bad things will happen if you don’t – mostly because the store will try to send you an event.

You can get the on-going code to my project from my GitHub repository.