{ React Router Introduction. }

Objectives

By the end of this chapter, you should be able to:

  • Define client side routing and the HTML 5 history API
  • Use react-router to build navigation for single page applications
  • Diagram how the first load of a client side application works

Introduction To Single Page Applications

So far all of our React applications have been composed of a few components. As our applications grow, we will need to change the components on the page. Often the changes happen because of a link that was clicked or because of data that has been submitted.

Any client-side (JavaScript) application that transitions between pages without refreshing the page or rendering a new HTML file from the server is called a Single-Page Application. On a well-designed SPA, the user should feel like the page still functions just like a standard server-based web app.

Specifically:

  • The browser back button should still work.
  • Adding a bookmark to a specific page should still work.
  • Even the first page load should essentially work the same way (we'll talk more about this one later).

You've already been utilizing SPA concepts already! By adding e.preventDefault() to your React forms, you are preventing the old-school behavior of HTML forms, which traditionally want to change pages on submission. In a SPA on the other hand, we keep the user on the same page and update the views dynamically with JavaScript and AJAX.

HTML 5 History API

So if JavaScript is handling all of the changes to the page, how can the browser back button still work? The answer is the HTML 5 history API. MDN has a good intro to history. Assume you are on a site with the following url: https://www.example.com. You can change the browser history with JavaScript:

// We can store some data we care about here
var state = { data: "value" };

// browser url will change to https://www.example.com/yoyo.html
window.history.pushState(state, "title not used right now", "yoyo.html");

// from javascript you can simulate clicking the browser back button
// The url in the browser will change back to https://www.example.com/
window.history.back();

If you want to build a SPA in JavaScript and include routing using the browser's back button, you will need to utilize the history API. However, you will rarely interface with this API directly; typically you'll be using some other tool that abstracts away any direct interaction with the history API. In the case of React, the tool we use to enable routing is called react-router.

Getting Started with React Router

Some frameworks like Angular and Ember come with a router, but since React is a much smaller library, it does not have its own router. To use client side routing with React, we're going to install a popular router called react-router. React Router has three separate packages:

  • react-router (base dependency package, not installed directly)
  • react-router-dom (this package is used for web development)
  • react-router-native (this package is used for mobile development).

Note: We won't be installing react-router directly. It will be installed as a dependency of react-router-dom.

So let's get started with react-router! Let's first create an application using create-react-app then add react-router-dom to it. In your terminal type the following:

create-react-app react-router-demo && cd react-router-demo
npm install --save react-router-dom

First, go to to the src/index.js file inside of your demo project. We are going to wrap the App component with a router. The code should look like this:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import registerServiceWorker from "./registerServiceWorker";
import "./index.css";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

registerServiceWorker();

There are a few things to take note of here. First, we imported BrowserRouter. The BrowserRouter, as the name implies, is a router designed for building single page applications in the browser. We'll highlight other types of routers momentarily, but in this course, BrowserRouter is typically the one you'll need.

Second, notice that our App component is now a child of BrowserRouter. It is important to take note that the router component we import from React Router expects exactly one child. Therefore whenever you use React Router, you will need to wrap your application in a router just like in the example above.

HashRouter vs BrowserRouter

There are a few router components that come with the React Router. Here is brief description on each one:

Router - This is the fundamental router component that React Router uses. You won't really be importing this directly, but higher-level routers depend on it.

Here's a list of the routers you're more likely to be working with:

BrowserRouter - This router component uses the HTML5 history API, and is the most commonly used high-level router when building SPAs for the browser with React and React Router.

HashRouter - This router is designed for support with older browsers that may not have access to the history API. In such cases, you can still get single-page type functionality by inserting an anchor (#) into the URL. However, this does not provide full backwards-compatibility: for this reason, the React Router documentation recommends BrowserRouter over HashRouter if possible.

To see the most obvious difference between HashRouter and BrowserRouter, let's change up our existing app so that import HashRouter in our index.js instead of BrowserRouter. When you use this router instead, you'll see that your URLs all have a hashtag (#) separating the host from the path. As we mentioned above, this can be useful as a fallback for older browsers that don't support the history API. For more on this, check out this article on the HTML5 history API.

NativeRouter - This router is designed for React Native applications.

MemoryRouter - This router mocks the history API by keeping a log of the browser history in memory. This can be helpful when writing tests, since tests are typically run outside of a browser environment.

StaticRouter - This is a router that never changes location. When would you ever use this? According to the docs, "This can be useful in server-side rendering scenarios when the user isn’t actually clicking around, so the location never actually changes. Hence, the name: static. It’s also useful in simple tests when you just need to plug in a location and make assertions on the render output."

BrowserRouter fallback

In order to use BrowserRouter, we need to specify a fallback - that is, what route to go to when a full refresh comes in (i.e. typing something in the browser bar and hitting enter). This can be done in the webpack.config.js and is handled for us when using create-react-app - you can read more about it here.

You won't need to worry about this when you're working locally, but may be an issue in production. The first time you deploy, you may also need to configure your remote server with a static.json file that tells the server how to handle requests to routes other than the root. Here's what that file would look like:

{
  "root": "build/",
  "routes": {
    "/**": "index.html"
  }
}

This tells your server to make the build directory your root, and to respond with the index.html file for every request. Without this file, if you deployed our example app and tried to go to /name/matt, the server would return a 404, since it doesn't know how to respond to this request! This is a route that only React Router understands.

By telling your server to respond to all requests with index.html, any requests made by manually entering an address in the URL bar are processed as follows:

  1. A GET request is made to your server with the path in the URL bar;
  2. Your server responds with index.html, which includes your bundled JavaScript files;
  3. Once your JavaScript loads, React Router takes over and reads the URL in the URL bar;
  4. Based on the current URL, React Router renders the appropriate components on the page.

Adding Routes

Our first React Router app is pretty boring so far. Let's makew things more interesting by adding some routes.

To do this, edit src/App.js to have the following structure.

import React from "react";
import "./App.css";
import { Route, Link } from "react-router-dom";

const Home = () => (
  <div>
    <h1>I'M HUNGRY</h1>
    <iframe
      src="https://giphy.com/embed/pBj0EoGSYjGms"
      width="480"
      height="288"
    />
  </div>
);

const Eat = () => (
  <div>
    <h1>NOM NOM NOM</h1>
    <iframe
      src="https://giphy.com/embed/VvQvOFqPjZLi"
      width="480"
      height="480"
    />
    <p>So tasty!</p>
  </div>
);

const Drink = () => (
  <div>
    <h1>SO REFRESHING</h1>
    <iframe
      src="https://giphy.com/embed/DbD6EnlEQmjTi"
      width="480"
      height="461"
    />
  </div>
);

const App = () => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/eat">Eat</Link>
      </li>
      <li>
        <Link to="/drink">Drink</Link>
      </li>
    </ul>
    <Route exact path="/" component={Home} />
    <Route path="/eat" component={Eat} />
    <Route path="/drink" component={Drink} />
  </div>
);

export default App;

As you may have noticed, this example uses two new components from React Router:

Route - this component basically acts as a translation service between routes and components. You tell it what path to look for in the URL, and what component it should render when it detects a match to that path. We can give this component quite a few more props aside from path, and component - we will see more later.

Notice that the Route for path="/" has the exact attribute. That tells react router that you only want to match the route exactly. Without the exact attribute, the content of the Home component will always show up, since / matches /, /eat, and /drink.

Link - this component is used as a replacement for the built-in anchor tag. If we're building a single-page application, anchor tags can't really be used, since they cause the page to reload. So when you're using React Router, you should be using <Link> instead of <a> to control links on the page.

Try running the application and clicking on the links. Notice that the address bar in the browser is changing. If you open up your network tab in chrome developer tools and check for HTTP requests, you should see that nothing is happening on the network and that the browser is not reloading the page.

So how does this work? Whenever you click on a Link component, react uses window.history to change the url in the address bar. The Route component renders the component specified in the component attribute whenever the current url path matches the path attribute.

Route props

In our example above, none of the components we wrote (Home, Eat, Drink, App) have any props. However, when you pass a component to Route, the component you pass automatically gets access to three props: match, location, and history. These props provide you with information about the route, the query string, and give you the ability to mutate the history. We'll examine these props in more detail as we go. For now, you can check out the docs on route props here. You can see these props more explicitly by using the React tab in the Chrome dev tools.

Alternatively, you can also display the props on the component directly. For example, suppose you modify the Eat component as follows:

const Eat = props => (
  <div>
    <h1>NOM NOM NOM</h1>
    <pre>{JSON.stringify(props, null, 4)}</pre>
    <iframe
      src="https://giphy.com/embed/VvQvOFqPjZLi"
      width="480"
      height="480"
    />
    <p>So tasty!</p>
  </div>
);

Now when you go to /eat, you should be able to see all of the props on the page.

URL Parameters and Query Strings

Once common use case for the route props is working with URL parameters. Just like we saw with server-side programming, we can design routes with dynamic URL parameters. In order to access these parameters, we use the match object given to us by React Router. More specifically, the any URL parameters will live inside of the match.params object as key-value pairs. We can access values in the query string using the location object given to us by React Router as well.

Let's see what that looks like. First, create a new file called src/ParamsExample.js. Inside of src/App.js, add a new link for the new component we will build:

import React from "react";
import "./App.css";
import { Route, Link } from "react-router-dom";
import ParamsExample from "./ParamsExample";

const Home = () => (
  <div>
    <h1>I'M HUNGRY</h1>
    <iframe
      src="https://giphy.com/embed/pBj0EoGSYjGms"
      width="480"
      height="288"
    />
  </div>
);

const Eat = () => (
  <div>
    <h1>NOM NOM NOM</h1>
    <iframe
      src="https://giphy.com/embed/VvQvOFqPjZLi"
      width="480"
      height="480"
    />
    <p>So tasty!</p>
  </div>
);

const Drink = () => (
  <div>
    <h1>SO REFRESHING</h1>
    <iframe
      src="https://giphy.com/embed/DbD6EnlEQmjTi"
      width="480"
      height="461"
    />
  </div>
);

const App = () => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/eat">Eat</Link>
      </li>
      <li>
        <Link to="/drink">Drink</Link>
      </li>
      <li>
        <Link to="/name">Instructors</Link>
      </li>
    </ul>
    <Route exact path="/" component={Home} />
    <Route exact path="/eat" component={Eat} />
    <Route path="/drink" component={Drink} />
    <Route path="/name" component={ParamsExample} />
  </div>
);

export default App;

Here we have added a Link to a fourth page, whose content is determined by ParamsExample. More specifically, we will render the ParamsExample for any path that starts with /name.

Next, in the src/ParamsExample.js file, add the following:

import React from "react";
import { Route, Link } from "react-router-dom";

const Instructor = ({ match, location }) => {
  const { name } = match.params;
  return (
    <div>
      <h3>Instructor Info For {name ? name : "No One"}</h3>
      <h4>
        What's in match? <pre>{JSON.stringify(match, null, 4)}</pre>
      </h4>
      <h4>
        What's in location? <pre>{JSON.stringify(location, null, 4)}</pre>
      </h4>
    </div>
  );
};

const ParamsExample = () => (
  <div>
    <h2>Instructors:</h2>
    <ul>
      <li>
        <Link to="/name/elie">Elie</Link>
      </li>
      <li>
        <Link to="/name/matt">Matt</Link>
      </li>
      <li>
        <Link to="/name/tim">Tim</Link>
      </li>
    </ul>
    <Route path="/name/:name" component={Instructor} />
  </div>
);

export default ParamsExample;

In src/ParamsExample.js, we have 2 components: ParmsExample and Instructor. The route renders an instructor component based the on the current path. In the instructor component, you can see that we're rendering a few special props: match and location. When we pass a component into Route using the component prop, that component will render with match and location props that expose information about the current path, including any URL parameters.

Parsing the query string with React Router v4

React Router does not ship with a built in way to parse a query string. The recommended action is to use the URLSearchParams constructor in the browser. Here is what that might look like:

let url = new URLSearchParams("https://infschool.com?
instructor=Michael&admissions=Angelina&favorite_dog=Whiskey")

url.get("admissions") // "Angelina"

You can see more about this here, and read about URLSearchParams here.

Passing information as props to a route

So far we have seen how to route to other components, and that the components we route to will have access to the router's props. But what if we want our components to have access to other props as well, not just those coming from the router?

There are a couple of ways to do this. One way us to create a stateless functional component on the fly inside of the component prop for Route. Let's see what that looks like:

import React from "react";
import "./App.css";
import { Route, Link } from "react-router-dom";

const Home = props => (
  <div>
    <h2>Hello {props.name}! </h2>
  </div>
);

const About = () => (
  <div>
    <h2>About</h2>
  </div>
);

const App = () => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/about">About</Link>
      </li>
    </ul>
    <Route
      exact
      path="/"
      component={props => <Home name="Elie" {...props} />}
    />
    <Route path="/about" component={About} />
  </div>
);

export default App;

Notice here that we are passing a function as the value of the component prop. Since all of our components are functions anyway, we simply wrap our <Home /> component with a function! This is exactly the idea around higher order components. We're not explicitly creating a higher order component, but we are wrapping our component with a function that passes down props to the <Home /> component.

Additional Resources

Simple React Router Tutorial

React Router repo

Exercise

Complete the react router Todo application

When you're ready, move on to React Router Continued

Continue

Creative Commons License