This chapter looks at how to implement React Router on React applications in order to build single-page applications. It starts by building simple nesting routing structures and then goes on to create parameters via URL parameters in the routes. This chapter will enable you to design missing pages by displaying an appropriate page not found message to the user. You will be equipped with techniques that will enable you to introduce navigation into your application. By the end of this chapter, you will have a solid foundation of using basic React Router.
When you are building a single-page application in any language or framework, it is typically not just one giant static page. It usually has links to help you navigate to the other components or sections of the application. A major part of the success of any such application goes to the design of the page-level manipulation, where there are different section links that enable you to navigate around the application with a sense of purpose. React remains no different in this regard. In fact, React has the ability to create a similar number of different URLs or paths that can be used to navigate around your app and to each of the individual components in a way that feels organic and intuitive to the user.
The goal of any single-page app is that the user should feel as though they are navigating through just a regular site but with far more interactivity. React Router goes a long way toward establishing that feeling by building a system for integrating different URLs seamlessly into your app but keeping the user within the confines of your React application.
This chapter includes a few hands-on approaches to explore the functionalities of React Router. Let's get started.
Modern web browsers have History and Location APIs that provide access, through the browser, to the different parts of the browser's history, location, and state. By using this functionality, you will be able to effectively handle routing. For example, the History API in modern browsers exposes the web browser's current session state and allows you to manipulate that state, either by moving forward using forward(), moving backward using back(), or moving to a specific point in the history using go().
The Location APIs allow JavaScript code to inspect the current location and path through the window.location.href and window.location.path properties and pull different types of information from it, allowing developers to figure out the navigation paths on a website and handle them accordingly.
React Router is a library for React that handles navigation and URLs and translates them into displaying the appropriate component for the URL.
React Router watches for state changes in the browser (via History), gets information about where the user currently is and where they're going (via Location), and uses the combination of the two, along with the JavaScript events, to build a set of routes and components to handle those routes. Let's go through the following exercise to build a basic React router using the Location API.
In this exercise, we will create a simplified router using the Location API. This will help us to understand a little more about how the React Router works, since being able to understand the underlying code concepts will help us to learn how to use libraries better:
$ npx create-react-app router
We will need to access a few properties via the Location API, specifically through window.location. Inside App.js, create a new React functional component called App, and display two key properties: window.location.href and window.location.pathname. Add the window.location.href and window.location.pathname properties as follows:
import React from 'react';
const App = () => (
<div className="Router">
<h1>Example Router</h1>
<hr />
<p>href: {window.location.href}</p>
<p>path: {window.location.pathname}</p>
</div>
);
export default App;
This component is just a standard functional component that will only display the two properties that we listed. The window.location.href property will display the full URL including the protocol and the domain name, while the window.location.pathname property will contain only the pathname variable. Between window.location.href and window.location.pathname, the former property will not be of much help; instead, we only care about the pathname variable.
As you can see from Figure 9.1, the path variable contains what we really need: that is, information about the domain and port that will tell us what route or component the user is trying to navigate to.
From here, we can start implementing our basic router, which should just be a function that returns a valid component. To do this, we are going to split the path property when a forward slash is encountered.
Note
The catch here is that the split operation will return both sides of the string, whether there is a value present or not.
/about will split at the forward slash, /, returning two values: a blank string and about. We also want our paths to be case-insensitive. We can do this by changing the pathnames to lowercase.
const RouteTo = path => {
const paths = path
.split('/')
.map(p => p.toLowerCase())
.slice(1);
switch (paths[0]) {
default:
return <div className="Default">Default Route</div>;
}
};
Note
The p argument in the map method is an ES6 arrow syntax for defining a function, while p is a temporary variable that gets declared within the scope of this function.
Inside the RouteTo utility function, we have created an array, called paths, that essentially contains two values: an empty string and about (in lowercase). We obtained these values after splitting path at the forward slash, /. We also have a default case where we have defined a route in case there are no paths to display.
const App = () => (
<div className="Router">
<h1>Example Router</h1>
<hr />
<p>href: {window.location.href}</p>
<p>path: {window.location.pathname}</p>
<hr />
{RouteTo(window.location.pathname)}
</div>
);
Now, let's implement a simple route. We will start off by adding one for /about, which will take us to an about page. The easiest way to do it is by adding a new functional About component.
const About = () => (
<div className="About">
<h1>About Page</h1>
<hr />
<p>Information about your app or who you are would go here.</p>
</div>
);
const RouteTo = path => {
const paths = path
.split('/')
.map(p => p.toLowerCase())
.slice(1);
switch (paths[0]) {
case 'about':
return <About />;
default:
return <div className="Default">Default Route</div>;
}
};
Note
The p argument inside the map method is an ES6 arrow syntax for defining a function, where p is a temporary variable that gets declared within the scope of this function.
As you can see from the preceding code, a switch statement has been introduced, where the paths array has been passed as an argument. While looping through paths, when about is encountered, the <About/> component gets displayed.
Now that we understand the very basics of how React Router works behind the scenes, let's move on to using React Router. The importance of doing this yourself the first time is to understand what is going on under the hood. Once you demystify complicated concepts, you can learn them much faster.
We have now looked at what we can accomplish through the browser's own built-in Location APIs. Imagine a library that is far more fully featured and built to accommodate the explicit purpose of making single-page applications way easier to navigate via browser mechanisms that the user is likely very comfortable with. The React Router library provides the functionalities that you require to make an application more interactive and intuitive. The library has gone through a few different revisions over the years, but the core of how to use it has remained similar across all iterations.
React Router provides several different built-in mechanisms for routing to components, handling default and error routes, and even more in-depth functionality such as dealing with authentication.
When we talk about React Router, a discussion of a few key components that will be imported and used while building the React Router project is necessary. So, let's get started:
Note
The order of the Route components in our application is important. The program will start matching the routes will match from top to the bottom and will return the first route that is applicable. This means that if you have the default route (that is, the one without a pathname variable specified for it) above the about route, then you will never be able to reach the about route. Therefore, we have to be careful while setting the order of these components.
The best way to learn React Router, however, is to start building with it. So, let's jump right into our next exercise and start building a simple route-based app.
In this exercise, we will create a new app that mirrors the functionality we implemented in the previous exercise.
We will start by building our App component as a functional component and include the Homepage component first, and then, eventually, we will add the About component. We are going to use the order of Router -> Switch -> Route in order to switch between these components. Perform the following steps to achieve this:
$ npx create-react-app router-example
$ yarn add react-router-dom
import React from 'react';
const About = () => (
<div className="About">
<h1>About Page</h1>
<hr />
<p>Information about your app or who you are would go here.</p>
</div>
);
export default About;
const Homepage = () => (
<div className="Homepage">
<h1>Homepage</h1>
<hr />
<p>This is our homepage.</p>
</div>
);
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
const App = () => (
<Router>
<Switch>
<Route>
<Homepage />
</Route>
</Switch>
</Router>
);
export default App;
This should give us the following application to start, regardless of what URL we add:
const App = () => (
<Router>
<Switch>
<Route path="/about">
<About />
</Route>
<Route>
<Homepage />
</Route>
</Switch>
</Router>
);
Here, we specify the path attribute for the route that sends the user to the About component and append the /about string in the URL Going to http://localhost:3000/about in our browser now will send us to the About component instead of the home page:
As you can see, the About page gets displayed when requested. You can see how easily we can use the Switch router in order to switch between the components.
Note
Routes are case-insensitive by default; so, if you went to http://localhost:3000/ABouT, it would still work and appropriately route you to the About component.
Frequently, while working with routes, we might encounter a problem where we need to customize what is displayed on a page based on a little bit of information being passed into the page or path. A good example of this is with something like search pages, where you might have a URL such as https://search.com?q=search_term, where q is the URL parameter and search_term is the value of that parameter.
Now, if we want to customize the display result based on the search_term value, you will need to do a little more legwork than just the simple routing example we used before. First of all, you will need to include a new import statement from react-router-dom called useLocation. useLocation allows you to parse out more details about the current location in the browser and convert the data into variables. Using this, we can pull the query parameters out via the useLocation utility, specifically from the search property on useLocation. We can then build a new URLSearchParams object and use that to pull the specific parameter we want. For example, if we wanted to pull the q URL parameter out, we could do so via the following snippet:
import { useLocation } from 'react-router-dom';
const Search = () => {
const query = new URLSearchParams(useLocation().search);
const term = query.get('q');
return (<div className="Search"></div>);
};
Note
In the preceding example, the call to useLocation().search function happens from inside a React functional component, Search.
Let's now put this into practice by building a quick and small Search component using the preceding snippet of code.
In this exercise, we will create a Search component that will be designed to display a search results page. We will create a results list and design a search query. We will then append this query as a parameter to the URL. We will then match this with the items in the results list. In the case of a match with an item from the results list, we will display the matched items on the page. To do this, let's go through the following steps:
$ npx create-react-app search
$ yarn add react-router-dom
import {
BrowserRouter as Router,
Switch,
Route,
useLocation
} from 'react-router-dom';
const Homepage = () => (
<div className="Homepage">
<h1>Homepage</h1>
<hr />
<p>This is our homepage.</p>
</div>
);
const App = () => (
<Router>
<Switch>
<Route path="/search">
<Search />
</Route>
<Route>
<Homepage />
</Route>
</Switch>
</Router>
);
const Search = props => {
const term = '';
return (
<div className="Search">
<h1>Search Page</h1>
<hr />
Found results for {term}:
<ul>
<li>No results.</li>
</ul>
</div>
);
};
const Items = [
'Lorem Ipsum',
'Ipsum Dipsum',
'Foo Bar',
'A little black cat',
'A lazy fox',
'A jumping dog'
];
const doSearch = term => {
if (!term) {
return Items;
}
return Items.filter(
item => item.toLowerCase().indexOf(term.toLowerCase()) !== -1
);
};
const Search = props => {
const query = new URLSearchParams(useLocation().search);
const term = query.get('q');
const returned = doSearch(term);
return (
<div className="Search">
<h1>Search Page</h1>
<hr />
Found results for {term}:
<ul>
{returned.map(t => (
<li key={t}>{t}</li>
))}
</ul>
</div>
);
};
As you can see, when we add a parameter in the search query, q=ipsum, that matches an item from the results list created in Step 8 of the preceding exercise, the page displays the items containing the word ipsum. (Note that the casing of the word is ignored.)
Let's move on to the next section, in which We will design components for instances where we might encounter a resource not found error when a user visits our site.
Sometimes, you will want to be able to display something meaningful to the user when they attempt to visit an incorrect URL for your site, instead of just showing the 404 error. This could be something funny, on-brand, or something memorable. However, in order to do something like this, you need to be able to catch those missed pages and display a NotFound component. One of the first things you will need to change is the previous default route that we had set up, which did not specify a pathname.
Try setting up your routes like this:
<Router>
<Switch>
<Route path="/search">
<Search />
</Route>
<Route>
<Homepage />
</Route>
</Switch>
</Router>
Here, you can see that there is no room for anything to fall through when someone enters a bad route when a component is not found. It is often better to be more explicit while working with routes:
<Router>
<Switch>
<Route exact path="/">
<Homepage />
</Route>
<Route path="/search">
<Search />
</Route>
</Switch>
</Router>
Notice that, in the preceding example, the path for the Homepage component is now listed above the Search component and has a few new properties attached to it. Specifically, we have added the following properties:
We can create an explicit catch-all route by using path= to allow it to catch any other routes entered. Let's put this to the test in the next exercise.
In this exercise, we will create a NotFound route in case a component does not have defined routes. This will allow us to display a custom 404 component. Let's go through the following steps:
$ npx create-react-app notfound
$ yarn add react-router-dom
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
const Homepage = () => (
<div className="Homepage">
<h1>Homepage</h1>
<hr />
<p>This is our homepage.</p>
</div>
);
const About = () => (
<div className="About">
<h1>About Page</h1>
<hr />
<p>Information about your app or who you are would go here.</p>
</div>
);
const App = () => (
<Router>
<Switch>
<Route exact path="/">
<Homepage />
</Route>
<Route path="/about">
<About />
</Route>
</Switch>
</Router>
);
const App = () => (
<Router>
<Switch>
<Route exact path="/">
<Homepage />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="*">
<h1>404 - Component Not Found</h1>
<a href="/">Return Home</a>
</Route>
</Switch>
</Router>
);
The NotFound component has displayed successfully. Now, let's move on to the Nesting Routes section.
Occasionally, you will need to create routes that have the same parent URLs but different sub URLs. For example, you might visit an online store, which might have a full URL like this:
https://somesite.com/store
When you click on an item in a store, they might be listed like this:
https://somesite.com/store/item/12345
Here, the 12345 value at the end of the URL indicates the ID of the item you are looking at on that online store. The site will want you to still see the information that is pertinent to you in the context of the store, so it will keep the parent route as store, and the child route as the specific item (the /item/12345 bit in the URL). This is an example of a nested route.
In React Router, dealing with nesting routes is a little bit more complicated than simple routes. The easiest way to handle it, even though it is slightly more verbose, is to place new Router -> Switch -> Route trees inside of your React components. Working with our previous exercise, if we wanted to add a nested Contact route to our About route, we would change our About component from a simple example to one that embeds the React Router components into the component itself.
The first thing you need to do is to wrap your About component in a Router component. Next, add a Switch statement inside of the component, followed by routes for each of the levels of nesting you want to include. We will also want to explore how we can use React Router's built-in Link component. The Link component acts like standard anchor tags in HTML, but it also helps you to preload necessary data for the component you are navigating to. It also does a better job of intercepting the navigation request in the browser so that only the component is refreshed instead of the entire page.
We also need to use something from React Router called useRouteMatch(). useRouteMatch() will return to us specifically two pieces of information that we need: the current path and the current full URL. We will need both to construct our nested paths and the links of the current path to avoid a situation where we change the about top-level path to something else and then have to go in and find every single nested route and fix those too.
Let's explore this further in our next exercise.
In this exercise, while building up more complicated navigation in your React apps, it is important to provide a means of navigation to move among the components in a simple way. We can use the Link component to provide React Router-specific navigation. You can copy the same project from the previous exercise, though, the steps to start a new project will be included here just in case. Let's get started:
$ npx create-react-app nested
$ yarn add react-router-dom
import { BrowserRouter as Router, Switch, Route, Link, useRouteMatch } from 'react-router-dom';
App.js
15 <div className="About">
16 <h1>About Page</h1>
17 <hr />
18 <p>Information about your app or who you are would go here.</p>
…
55 const Homepage = () => (
56 <div className="Homepage">
57 <h1>Homepage</h1>
58 <hr />
59 <p>This is our homepage.</p>
60 </div>
61 );
The complete code can be found here: https://packt.live/2LqH4sp
const Navbar = () => (
<div className="Navbar">
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</div>
);
const App = () => (
<div className="App">
<Router>
<Navbar />
<Switch>
<Route exact path="/">
<Homepage />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="*">
<h1>404 - Component Not Found</h1>
</Route>
</Switch>
</Router>
</div>
);
Our app should now resemble the following screenshot:
const Bio = () => (
<div className="Bio">
<h2>Bio</h2>
<hr />
<p>I'm a pretty cool person.</p>
</div>
);
const Contact = () => (
<div className="Contact">
<h2>Contact Me</h2>
<hr />
<p>Send me an email at [email protected].</p>
</div>
);
const About = () => {
return (
<Router>
<div className="About">
<h1>About Page</h1>
<hr />
<p>Information about your app or who you are would go here.</p>
</div>
</Router>
);
};
const { path, url } = useRouteMatch();
<hr />
<Switch>
<Route path={`${path}/contact`}>
<Contact />
</Route>
<Route path={`${path}/bio`}>
<Bio />
</Route>
</Switch>
<hr />
<Link to={`${url}`}>About Home</Link>
<Link to={`${url}/contact`}>Contact</Link>
<Link to={`${url}/bio`}>Bio</Link>
This should give us a full About component with the following code:
const About = () => {
const { path, url } = useRouteMatch();
return (
<Router>
<div className="About">
<h1>About Page</h1>
<hr />
<p>Information about your app or who you are would go here.</p>
<hr />
<Link to={`${url}`}>About Home</Link>
<Link to={`${url}/contact`}>Contact</Link>
<Link to={`${url}/bio`}>Bio</Link>
<hr />
<Switch>
<Route path={`${path}/contact`}>
<Contact />
</Route>
<Route path={`${path}/bio`}>
<Bio />
</Route>
</Switch>
</div>
</Router>
);
};
And a resulting component that should resemble the following screenshot:
As you can see, the About and Bio components have been displayed successfully. We are now going to put everything we have learned so far together and perform the following activity.
The goal of this activity is to construct a simple e-commerce application where we will implement several links or routes that could be used to navigate to different sections. We will apply nested 404/component not found routes and URL queries.
In this MyStore we will have a few items displayed. The store app should contain a header and the list of items that are up for sale. You can either delegate the display of each item to another component or write them into the Store component. The Store component has a couple of requirements:
The following steps will help you to complete the activity:
The output should be as follows:
You have successfully completed the activity.
React Router is a very powerful tool to have in your React toolbox. With it, you can create incredibly seamless and snappy web applications that will feel like they are practically native applications. Notice that every time you navigate around using the navigation bar created via Link components, the entire browser doesn't redraw the page for each different navigation element; instead, only the parts of the tree that need to be re-rendered will be re-rendered and the rest will remain the same.
This gives the user the feeling of navigation that feels natural, which is one of the best things you can give to your app. The more seamless everything feels, the more likely the user will be happy using it and recommend it to others as well. Putting this together with the techniques you have learned over the course of this chapter will give you the ability to construct some truly fantastic experiences on the web. In the next chapter, we will look into React Router in more detail.
Note
The solution to this activity can be found on page 672.
Over the course of this chapter, we explored the basics of React Router and the various Router implementation strategies that you can take when you are just getting started. It is important to have a better means of natural navigation via a browser; this allows for a clean separation of components into their logical use cases and allows the user to use their back button in the browser. It is a better, cleaner separation for users and for developers, and now we have a great working knowledge not just of the library itself, but also a little insight into how it works behind the scenes as well.
In later chapters, we will explore React Router in greater detail, showcasing some of the more advanced functionality. We will be able to take our implementations with React Router from basic applications to the next level and build upon the foundations that we have established in this chapter.