Creating a Single-Page App in React using React Router

by kirupa   |  10 October 2016

Now that you've familiarized yourself with the basics of how to work with React, let's kick things up a few notches. What we are going to do is use React to build a simple single-page app (also referred to as SPA by the cool kids...and people living in Scandinavia). Like we talked about in our React introduction forever ago, single-page apps are different from the more traditional multi-page apps that you see everywhere. The biggest difference is that navigating a single-page app doesn't involve going to an entirely new page. Instead, your pages (commonly known as views in this context) typically load inline within the same page itself:

When you are loading content inline, things get a little challenging. The hard part is not loading the content itself. That is relatively easy. The hard part is making sure that single-page apps behave in a way that is consistent with what your users are used to. More specifically, when users navigate your app, they expect that:

  1. The URL displayed in the address bar always reflects the thing that they are viewing.
  2. They can use the browser's back and forward buttons...successfully.
  3. They can navigate to a particular view (aka deep link) directly using the appropriate URL.

With multi-page apps, these three things come for free. There is nothing extra you have to do for any of it. With single-page apps, because you aren't navigating to an entirely new page, you have to do real work to deal with these three things that your users expect to just work. You need to ensure that navigating within your app adjusts the URL appropriately. You need to ensure your browser's history is properly synchronized with each navigation to allow users to use the back and forward buttons. If users bookmark a particular view or copy/paste a URL to access later, you need to ensure that your single-page app takes the user to the correct place.

To deal with all of this, you have a bucket full of techniques commonly known as routing. Routing is where you try to map URLs to destinations that aren't physical pages such as the individual views in your single-page app. That sounds complicated, but fortunately there are a bunch of JavaScript libraries that help us out with this. One such JavaScript library is the star of this tutorial, React Router. React Router provides routing capabilities to single-page apps built in React, and what makes it nice is that extends what you already know about React in familiar ways to give you all of this routing awesomeness. In this tutorial, you'll learn all about how it does that...and hopefully more!

Onwards!

OMG! A React Book Written by Kirupa?!!

To kick your React skills up a few notches, everything you see here and more (with all its casual clarity!) is available in both paperback and digital editions.

BUY ON AMAZON

The Example

Before we go further, take a look at the following example:

What you have here is a simple React app that uses React Router to provide all of the navigation and view-loading goodness! Click on the various links to load the relevant content, and feel free to open up this page in its own browser window to use the back and forward buttons to see them working.

In the following sections, we are going to be building this app in pieces. By the end, not only will you have re-created this app, you'll hopefully have learned enough about React Router to build cooler and more awesomer things.

Building the App

The first thing we need to do is get the boilerplate markup and code for our app up and running. Create a new HTML document and add the following content into it:

<!DOCTYPE html>
<html>
 
<head>
  <script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
  <script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
 
  <style>
 
  </style>
</head>
 
<body>
 
  <div id="container">
 
  </div>
 
  <script type="text/babel">
    var destination = document.querySelector("#container");
 
    ReactDOM.render(
      <div>
        Hello!
      </div>,
      destination
    );
  </script>
</body>
 
</html>

This starting point is almost the same as what you've seen for all of our other examples. This is just a nearly blank app that happens to load the React and React-DOM libraries. If you preview what you have in your browser, you'll see a very lonely Hello! displayed.

Still Keeping Things Simple

For now, we are continuing to rely on having our browser do all of the heavy lifting. We'll look into changing that up with a "modern" build process later, so enjoy the simplicity for now :P

Next, because React Router isn't a part of React itself, we need to add a reference to it. In our markup, find where we have our existing script references and add the following highlighted line:

<script src="https://unpkg.com/react@15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://npmcdn.com/react-router@2.4.0/umd/ReactRouter.min.js"></script>   

By adding the highlighted line, we ensure the React Router library is loaded alongside the core React, ReactDOM, and Babel libraries. At this point, we are in a good state to start building our app and taking advantage of the sweet functionality React Router brings to the table.

Displaying the Initial Frame

When building a single-page app, there will always be a part of your page that will remain static. This static part, also referred to as an app frame, could just be one invisible HTML element that acts as the container for all of your content, or could include some additional visual things like a header, footer, navigation, etc. In our case, our app frame will involve our navigation header and an empty area for content to load in. To display this, we are going to create a component that is going to be responsible for this.

Inside your script tag just above your ReactDOM.render call, go ahead and the following chunk of JSX:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li>Home</li>
          <li>Stuff</li>
          <li>Contact</li>
        </ul>
        <div className="content">

        </div>
      </div>
    )
  }
});

Once you have pasted this, take a look at what we have here. We have is a component called App that returns some HTML. To see what this HTML looks like, modify your ReactDOM.render call to reference this component instead of displaying the word Hello!. Go ahead and make the following highlighted change:

ReactDOM.render(
  <div>
    <App/>
  </div>, 
  destination
);

Once you have done this, preview your app in the browser. You should see an unstyled version of an app title and some list items appear:

I know that this doesn't look all fancy and styled, but that's OK for now. We will deal with that later. Going a bit deeper, what we've done is just create a component called App and display it via our ReactDOM.render call. The important thing to call out is that there is nothing React Router specific here. ABSOLUTELY NOTHING! This is straight-up React 101. Let's fix that by throwing React Router into the mix. Replace the contents of your ReactDOM.render call with the following:

ReactDOM.render(
  <ReactRouter.Router>
    <ReactRouter.Route path="/" component={App}>

    </ReactRouter.Route>
  </ReactRouter.Router>,
  destination
);

Ignore how strange everything looks for a moment, and just preview your app in the browser after you've made this change. If everything worked out properly, you will see your App component displayed just like you saw earlier. Now, let's figure out why that is the case by learning more about what exactly is going on here. This is where we deviate a bit from core React concepts and learn things specific to React Router itself.

First, what we did is specify our Router component:

ReactDOM.render(
  <ReactRouter.Router>
    <ReactRouter.Route path="/" component={App}>

    </ReactRouter.Route>
  </ReactRouter.Router>,
  destination
);    

The Router component is part of the React Router API, and its job is to deal with all of the routing-related logic our app will need. Inside this component, we specify what is known as the routing configuration. That is a fancy term that people use to describe the mapping between URLs and the views. The specifics of that are handled by another component called Route:

ReactDOM.render(
  <ReactRouter.Router>
    <ReactRouter.Route path="/" component={App}>

    </ReactRouter.Route>
  </ReactRouter.Router>,
  destination
);    

The Route component takes several props that help define what to display at what URL. The path prop specifies the URL we are interested in matching. In this case, it is the root aka /. The component prop allows you to specify the name of the component you wish to display. For this example, it is our App component. Putting this all together, what this Route says is as follows: If the URL you are on contains the root, go ahead and display the App component. Because this condition is true when you preview your app, you see the result of what happens when your App component renders.

Displaying the Home Page

As you can sorta kinda see, the way React Router provides you with all of this routing functionality is by mimicking concepts in React you are already familiar with – namely components, props, and JSX. What we have right now for displaying our app's frame is a great example of this. Now, it's time to go even further. What we want to do next is define the content that we will display as part of our home view.

To do this, we are going to create a component called Home that is going to contain the markup we want to display. Just above where you have your App component defined, add the following:

var Home = React.createClass({
  render: function() {
      return (
        <div>
          <h2>HELLO</h2>
          <p>Cras facilisis urna ornare ex volutpat, et
          convallis erat elementum. Ut aliquam, ipsum vitae
          gravida suscipit, metus dui bibendum est, eget rhoncus nibh
          metus nec massa. Maecenas hendrerit laoreet augue
          nec molestie. Cum sociis natoque penatibus et magnis
          dis parturient montes, nascetur ridiculus mus.</p>
 
          <p>Duis a turpis sed lacus dapibus elementum sed eu lectus.</p>
        </div>
      );
    }
});   

As you can see, our Home component doesn't do anything special. It just returns a blob of HTML. Now, what we want to do is display the contents of our Home component when the page loads. This component is the equivalent of our app's "home page". The way we do this is simple. Inside our App component, we have a div with a class value of content. We are going to load our Home component inside there.

The obvious solution might look something like this:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li>Home</li>
          <li>Stuff</li>
          <li>Contact</li>
        </ul>
        <div className="content">
          <Home/>
        </div>
      </div>
    )
  }
});   

Notice that we define our Home component inside that content div. If you preview your app, things will even seem to work as expected:

You see your navigation header, and then you see the contents of our Home component. While this approach works, it is actually the wrong thing to do. It is wrong because it complicates our desire to load other pieces of content as the user is navigating around our app. We've essentially hard-coded our app to only display the Home component. That's a problem, but we'll come back to that in a little bit.

Interim Cleanup Time

Before we continue making progress on our app, let's take a short break and make some stylistic improvements to what we have so far.

Adding the CSS

Right now, our app looks very plain...and like something straight out of the 1800's. To fix this, we are going to rely on our dear old friend, CSS. Inside the style tag, go ahead and add the following style rules:

body {
  background-color: #FFCC00;
  padding: 20px;
  margin: 0;
}
h1, h2, p, ul, li {
  font-family: Helvetica, Arial, sans-serif;
}
ul.header li {
  display: inline;
  list-style-type: none;
  margin: 0;
}
ul.header {
  background-color: #111;
  padding: 0;
}
ul.header li a {
  color: #FFF;
  font-weight: bold;
  text-decoration: none;
  padding: 20px;
  display: inline-block;
}
.content {
  background-color: #FFF;
  padding: 20px;
}
.content h2 {
  padding: 0;
  margin: 0;
}
.content li {
  margin-bottom: 10px;
}    

Yes, we are using CSS in its markup form. We aren't doing the inline style object approach that we've used in the past. The reason has to do with convenience. Our components aren't going to be re-used outside of our particular app, and we really want to take advantage of CSS inheritance to minimize duplicated markup. Otherwise, if we didn't use regular CSS, we'll end up with a bunch of giant style objects defined for almost every element in our markup. That would make even the most patient among us annoyed when reading the code.

Anyway, once you have added all of this CSS, our app will start to look much better:

There is still some more work to be done (for example, our navigation links disappeared behind the black banner), but we'll fix all of those up in a little bit.

Avoiding the ReactRouter Prefix

We have just one more cleanup related task before we return to our regularly scheduled programming. Have you noticed that every single time we call something defined by the React Router API, we prefix that something with the word ReactRouter?

<ReactRouter.Router>
  <ReactRouter.Route path="/" component={App}>

  </ReactRouter.Route>
</ReactRouter.Router>

That is a bit verbose to have to repeat for every API call we make, and this is going to be more of a problem as we dive further into the React Router API and use more things from inside it.

The fix for this involves using a new ES6 trick where you can manually specify which values will automatically get prefixed. Towards the top of your script tag, add the following:

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

Once you've added this code, every time you use one of the values defined inside the brackets, the prefix ReactRouter will automatically be added for you when your app runs. This means, you can now go back to your ReactDOM.render method and remove the ReactRouter prefix from our Router and Route component instances:

ReactDOM.render(
  <Router>
    <Route path="/" component={App}>

    </Route>
  </Router>,
  destination
);    

If you preview your app now, nothing really should change. The end result is identical to what you had before. The only difference is that our markup is a bit more compact.

Now, before we move on, you are probably wondering why the list of values that will automatically be prefixed with ReactRouter contains a whole bunch of things beyond the Router and Route values that we have used in our code so far. Think of these additional values as a preview of the other parts of the React Router API we will be using shortly. Spoiler alert! (Probably too late to mention that now, eh?)

Displaying the Home Page Correctly

We ended a few sections ago by saying that the way we currently have our home page displayed is incorrect. While you get the desired result when our page loads, this approach doesn't really make it easy for us to load anything other than the home page when users navigate around. The call to our Home component is hard coded inside App.

The correct solution involves letting React Router handle which component to call depending on what your current URL structure is. This involves nesting Route components inside Route components to better define the URL-to-view mapping further. Go back to our ReactDOM.render method, and make the following highlighted change:

ReactDOM.render(
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
    </Route>
  </Router>,
  destination
);

Inside our root Route element, we are defining another route element of type IndexRoute (more on who this is in a second!) and setting its view to be our Home component. There is one more change we need to make. Inside our App component, remove the call to the Home component and replace it with the following highlighted line:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li>Home</li>
          <li>Stuff</li>
          <li>Contact</li>
        </ul>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    )
  }
});

If you preview your page now, you will still see your home content displayed. The difference this time is that we are displaying the home content properly in a way that doesn't prevent other content from being displayed instead. This is because of two things:

  1. What gets displayed inside App is controlled by the result of this.props.children instead of a hard-coded component.
  2. Our Route element inside ReactDOM.render contains an IndexRoute element whose sole purpose for existing is to declare which component will be displayed when your app initially loads.

All of this may seem even more bizarre than what you expected a few moments ago, but things will make more sense as we use these various APIs more in the following sections.

Creating the Navigation Links

Right now, we just have our frame and home view setup. There isn't really anything else for a user to do here outside of just seeing what we have set as the home "page". Let's fix that by creating some navigation links. More specifically, let's linkify the navigation elements we already have:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li>Home</li>
          <li>Stuff</li>
          <li>Contact</li>
        </ul>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    )
  }
});   

If you aren't sure why these elements aren't visible when you preview your page, that's because they blended in with the black background once we added the CSS in. No biggie there. We'll fix that in a few, but first let's talk about how we are going to turn these elements into links.

The way you specify navigation links in React Router isn't by directly using the tried and tested a tag and throwing in a path via the href attribute. Instead, you specify your navigation link using React Router's Link components that are similar to a tags but offer a lot more functionality. To see the Link component in action, go ahead and modify our existing navigation elements to look like the following highlighted lines:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li><Link to="/">Home</Link></li>
          <li><Link to="/stuff">Stuff</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    )
  }
});   

Notice what have done here. Our Link components specify a prop called to. This prop specifies the value of the URL we will display in the address bar. Indirectly, it also specifies the location we will be telling React Router we are virtually navigating to. Our Home link takes users to the root ( / ), the Stuff link takes users to a location called stuff, and the Contact link takes users to a location called contact.

If you preview your page and click on the links (which will now be visible because the CSS for them will have kicked in), you won't see anything new display. You will just see your Home content because that is all that we had specified earlier. With that said, you can see the URLs updating in the address bar. You'll see your current page followed by a #/contact, #/stuff, or #/ depending on which of the links you clicked. That is progress!

Adding the Stuff and Contact Views

Our app is slowly taking its final shape...or it will get really close by the time we are done with this section! What we are going to do next is define the components for our Stuff and Contact views that we linked to earlier. In your code just below where you have your Home component, go ahead and add in the following:

var Contact = React.createClass({
  render: function() {
      return (
        <div>
          <h2>GOT QUESTIONS?</h2>
          <p>The easiest thing to do is post on
          our <a href="http://forum.kirupa.com">forums</a>.
          </p>
        </div>
      );
    }
});

var Stuff = React.createClass({
  render: function() {
      return (
        <div>
          <h2>STUFF</h2>
          <p>Mauris sem velit, vehicula eget sodales vitae,
          rhoncus eget sapien:</p>
          <ol>
            <li>Nulla pulvinar diam</li>
            <li>Facilisis bibendum</li>
            <li>Vestibulum vulputate</li>
            <li>Eget erat</li>
            <li>Id porttitor</li>
          </ol>
        </div>
      );
    }
});   

What we have just added are the Stuff and Contact components that simply render out HTML. All that remains is for us to update our routing configuration to include these two components and display them at the appropriate URL.

In our ReactDOM.render method, go ahead and add the following two highlighted lines:

ReactDOM.render(
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="stuff" component={Stuff} />
      <Route path="contact" component={Contact} />
    </Route>
  </Router>,
  destination
);    

All we are doing here is updating our routing logic to display the Stuff component if the URL contains the word stuff and to display the Contact component if the URL contains the word contact. If you preview your page now, click on the Stuff and Contact links. If everything worked out fine, you'll see these views get loaded inside our app frame when you navigate to them.

A Little Bit About Route Matching

Our route configuration is nothing more than a series of rules that determine what to do when a URL matches the conditions we have laid out. The fancy term for that is route matching. The heuristic React Router uses to match URLs is fully explained in the React Router documentation, but for our case, we have a simple nested route where you can have multiple things that can match at the same time. Our outer route matches if the URL contains /. Our inner routes then match if the URL happens to contain stuff or contact.

What this means is simple. For each route that matches, the component that you specified to display will appear. When you are navigating to a page like /stuff, the App component will display because the / exists in the URL. The Stuff component then displays because the path for stuff is in the URL as well. That is why when we navigate to the Stuff or Contact pages, we see them in addition to our frame. You can have deeply nested routes as well.

Take a look at the following configuration:

ReactDOM.render(
  <Router>
    <Route path="/" component={App}>
      <IndexRoute component={Home} />
      <Route path="stuff" component={Stuff}>
        <Route path="blah" component={MyBlah}/>
      </Route>
      <Route path="contact" component={Contact} />
    </Route>
  </Router>,
  destination);

In this example, notice that our Route element whose path is stuff now contains a nested route for a path containing blah. This means if you happened to have a URL that is /stuff/blah, the MyBlah component will be displayed in addition to the Stuff component and the App component from the parent routes matching.

By nesting routes and following the route matching rules, you can display custom views depending for a variety of URL arrangements you may expose in your app for your users to navigate to.

Creating Active Links

The last thing we are going to tackle is something that greatly increases the usability of our app. Depending on which page you are currently displaying, we are going to highlight that link with a blue background. For example, the following is what our app will look like when the Stuff content is being displayed:

 

The way you accomplish this in React Router is by setting a prop called activeClassName on your Link instances with the name of the CSS class that will get set when that link is currently active. To make this happen, go back to your App component and make the highlighted changes:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li><Link to="/" activeClassName="active">Home</Link></li>
          <li><Link to="/stuff" activeClassName="active">Stuff</Link></li>
          <li><Link to="/contact" activeClassName="active">Contact</Link></li>
        </ul>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    )
  }
});   

We specify the activeClassName prop and set it to a value of active. This ensures that whenever a link is clicked (and its path becomes active), the link element's class attribute at runtime gets set to a value of active. To ensure our active links are styled differently, go ahead and add the following CSS:

.active {
  background-color: #0099FF;
}     

If you preview your app now, click on any of the links. Notice that the active link displays with a blue background. We aren't done just yet, though. Our Home link is always highlighted. It should only be highlighted when we load our home page for the first time or explicitly navigate to the Home link itself. To fix this, we need to change how we link to our Home content. Instead of specifying our Home content with a Link element, we are going to replace it with an IndexLink element instead.

Go ahead and make this change:

var App = React.createClass({
  render: function() {
    return (
      <div>
        <h1>Simple SPA</h1>
        <ul className="header">
          <li><IndexLink to="/" activeClassName="active">Home</IndexLink></li>
          <li><Link to="/stuff" activeClassName="active">Stuff</Link></li>
          <li><Link to="/contact" activeClassName="active">Contact</Link></li>
        </ul>
        <div className="content">
          {this.props.children}
        </div>
      </div>
    )
  }
});

Once your Home navigation element is represented by an IndexLink instead of a Link, preview your app again. This time, when the app loads, you'll notice that your Home link has the cool blue background by default. When you navigate to the Stuff or Contact pages, the Home link no longer has the highlight applied. And with this, your app is mostly good to go!

Conclusion

By now, we've covered a good chunk of the cool functionality React Router has for helping you build your single-page apps. This doesn't mean that there aren't more interesting things for you to take advantage of. Our app was pretty simple with very modest demands on what routing functionality we needed to implement. There is a whole lot more that React Router provides, so if you are building a more complex single-page app than what we've looked at so far, you should totally spend an afternoon taking a look the full React Router documentation and examples.

If you have a question about this or any other topic, the easiest thing is to drop by our forums where a bunch of the friendliest people you'll ever run into will be happy to help you out!

THE KIRUPA NEWSLETTER

Get cool tips, tricks, selfies, and more...personally hand-delivered to your inbox!

( View past issues for an idea of what you've been missing out on all this time! )

WHAT DO YOU THINK?

blog comments powered by Disqus

NEWSLETTER

No spam. No fluff. Just awesome content sent straight to your inbox!

Awesome and high-performance web hosting!
BACK TO TOP
new books - yay!!!