Translate

Tuesday, 27 October 2020

React Router v5: The Complete Guide

React Router v5: The Complete Guide

React Router is the de facto standard routing library for React. When you need to navigate through a React application with multiple views, you’ll need a router to manage the URLs. React Router takes care of that, keeping your application UI and the URL in sync.

This tutorial introduces you to React Router v5 and a whole lot of things you can do with it.

Introduction

React is a popular library for creating single-page applications (SPAs) that are rendered on the client side. An SPA might have multiple views (aka pages), and unlike conventional multi-page apps, navigating through these views shouldn’t result in the entire page being reloaded. Instead, we want the views to be rendered inline within the current page. The end user, who’s accustomed to multi-page apps, expects the following features to be present in an SPA:

  • Each view should have a URL that uniquely specifies that view. This is so that the user can bookmark the URL for reference at a later time. For example, www.example.com/products.
  • The browser’s back and forward button should work as expected.
  • Dynamically generated nested views should preferably have a URL of their own too — such as example.com/products/shoes/101, where 101 is the product ID.

Routing is the process of keeping the browser URL in sync with what’s being rendered on the page. React Router lets you handle routing declaratively. The declarative routing approach allows you to control the data flow in your application, by saying “the route should look like this”:

<Route path="/about">
  <About />
</Route>

You can place your <Route> component anywhere you want your route to be rendered. Since <Route>, <Link> and all the other React Router APIs that we’ll be dealing with are just components, you can easily get up and running with routing in React.

Note: there’s a common misconception that React Router is an official routing solution developed by Facebook. In reality, it’s a third-party library that’s widely popular for its design and simplicity.

Overview

This tutorial is divided into different sections. First, we’ll set up React and React Router using npm. Then we’ll jump right into some React Router basics. You’ll find different code demonstrations of React Router in action. The examples covered in this tutorial include:

  • basic navigational routing
  • nested routing
  • nested routing with path parameters
  • protected routing

All the concepts connected with building these routes will be discussed along the way.

The entire code for the project is available on this GitHub repo.

Let’s get started!

Setting up React Router

To follow along with this tutorial, you’ll need a recent version of Node installed on your PC. If this isn’t the case, then head over to the Node home page and download the correct binaries for your system. Alternatively, you might consider using a version manager to install Node. We have a tutorial on using a version manager here.

Node comes bundled with npm, a package manager for JavaScript, with which we’re going to install some of the libraries we’ll be using. You can learn more about using npm here.

You can check that both are installed correctly by issuing the following commands from the command line:

node -v
> 12.19.0

npm -v
> 6.14.8

With that done, let’s start off by creating a new React project with the Create React App tool. You can either install this globally, or use npx, like so:

npx create-react-app react-router-demo

When this has finished, change into the newly created directory:

cd react-router-demo

The React Router library comprises three packages: react-router, react-router-dom, and react-router-native. The core package for the router is react-router, whereas the other two are environment specific. You should use react-router-dom if you’re building a website, and react-router-native if you’re in a mobile app development environment using React Native.

Use npm to install react-router-dom:

npm install react-router-dom

Then start the development server with this:

npm run start

Congratulations! You now have a working React app with React Router installed. You can view the app running at http://localhost:3000/.

React Router Basics

Now let’s familiarize ourselves with a basic React Router setup. To do this, we’ll make an app with three separate views: Home, Category and Products.

The Router Component

The first thing we’ll need to do is to wrap our <App> component in a <Router> component (provided by React Router). Since we’re building a browser-based application, we can use two types of router from the React Router API:

The primary difference between them is evident in the URLs they create:

// <BrowserRouter>
http://example.com/about

// <HashRouter>
http://example.com/#/about

The <BrowserRouter> is the more popular of the two because it uses the HTML5 History API to keep your UI in sync with the URL, whereas the <HashRouter> uses the hash portion of the URL (window.location.hash). If you need to support legacy browsers that don’t support the History API, you should use <HashRouter>. Otherwise <BrowserRouter> is the better choice for most use cases. You can read more about the differences here.

So, let’s import the BrowserRouter component and wrap it around the App component:

// src/index.js

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

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

The above code creates a history instance for our entire <App> component. Let’s look at what that means.

A Little Bit of History

The history library lets you easily manage session history anywhere JavaScript runs. A history object abstracts away the differences in various environments and provides a minimal API that lets you manage the history stack, navigate, and persist state between sessions. — React Training docs

Each <Router> component creates a history object that keeps track of the current location (history.location) and also the previous locations in a stack. When the current location changes, the view is re-rendered and you get a sense of navigation. How does the current location change? The history object has methods such as history.push and history.replace to take care of that. The history.push method is invoked when you click on a <Link> component, and history.replace is called when you use a <Redirect>. Other methods — such as history.goBack and history.goForward — are used to navigate through the history stack by going back or forward a page.

Moving on, we have Links and Routes.

Link and Route Components

The <Route> component is the most important component in React Router. It renders some UI if the current location matches the route’s path. Ideally, a <Route> component should have a prop named path, and if the path name matches the current location, it gets rendered.

The <Link> component, on the other hand, is used to navigate between pages. It’s comparable to the HTML anchor element. However, using anchor links would result in a full page refresh, which we don’t want. So instead, we can use <Link> to navigate to a particular URL and have the view re-rendered without a refresh.

Now we’ve covered everything you need to make our app work. Update src/App.js as follows:

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

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

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

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

export default function App() {
  return (
    <div>
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/category">Category</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>

      { /* Route components are rendered if the path prop matches the current URL */}
      <Route path="/"><Home /></Route>
      <Route path="/category"><Category /></Route>
      <Route path="/products"><Products /></Route>
    </div>
  );
}

Here, we’ve declared the components for Home, Category and Products inside App.js. Although this is okay for now, when a component starts to grow bigger, it’s better to have a separate file for each component. As a rule of thumb, I usually create a new file for a component if it occupies more than 10 lines of code. Starting from the second demo, I’ll be creating a separate file for components that have grown too big to fit inside the App.js file.

Inside the App component, we’ve written the logic for routing. The <Route>‘s path is matched with the current location and a component gets rendered. Previously, the component that should be rendered was passed in as a second prop. However, recent versions of React Router have introduced a new route rendering pattern, whereby the component(s) to be rendered are children of the <Route>.

Here / matches both / and /category. Therefore, both the routes are matched and rendered. How do we avoid that? You should pass the exact prop to the <Route> with path='/':

<Route exact path="/">
  <Home />
</Route>

If you want a route to be rendered only if the paths are exactly the same, you should use the exact prop.

Nested Routing

To create nested routes, we need to have a better understanding of how <Route> works. Let’s look at that now.

As you can read on the React Router docs, the recommended method of rendering something with a <Route> is to use children elements, as shown above. There are, however, a few other methods you can use to render something with a <Route>. These are provided mostly for supporting apps that were built with earlier versions of the router before hooks were introduced:

  • component: when the URL is matched, the router creates a React element from the given component using React.createElement.
  • render: handy for inline rendering. The render prop expects a function that returns an element when the location matches the route’s path.
  • children: this is similar to render, in that it expects a function that returns a React component. However, children gets rendered regardless of whether the path is matched with the location or not.

Path and Match

The path prop is used to identify the portion of the URL that the router should match. It uses the Path-to-RegExp library to turn a path string into a regular expression. It will then be matched against the current location.

If the router’s path and the location are successfully matched, an object is created which is called a match object. The match object contains more information about the URL and the path. This information is accessible through its properties, listed below:

  • match.url: a string that returns the matched portion of the URL. This is particularly useful for building nested <Link> components.
  • match.path: a string that returns the route’s path string — that is, <Route path="">. We’ll be using this to build nested <Route> components.
  • match.isExact: a Boolean that returns true if the match was exact (without any trailing characters).
  • match.params: an object containing key/value pairs from the URL parsed by the Path-to-RegExp package.

Implicit Passing of Props

Note that when using the component prop to render a route, the match, location and history route props are implicitly passed to the component. When using the newer route rendering pattern, this is not the case.

For example, take this component:

const Home = (props) => {
  console.log(props);

  return (
    <div>
      <h2>Home</h2>
    </div>
  );
};

Now render the route like so:

<Route exact path="/" component={Home} />

This will log the following:

{
  history: { ... }
  location: { ... }
  match: { ... }
}

But now instead render the route like so:

<Route exact path="/"><Home /></Route>

This will log the following:

{}

This might seem disadvantageous at first, but worry not! React v5.1 introduced several hooks to help you access what you need, where you need it. These hooks give us new ways to manage our router’s state and go quite some way to tidying up our components.

I’ll be using some of these hooks throughout this tutorial, but if you’d like a more in-depth look, check out the React Router v5.1 release announcement. Please also note that hooks were introduced in version 16.8 of React, so you’ll need to be on at least that version to use them.

The Switch Component

Before we head to the demo code, I want to introduce you to the Switch component. When multiple <Route>s are used together, all the routes that match are rendered inclusively. Consider this code from demo 1. I’ve added a new route to demonstrate why <Switch> is useful:

<Route exact path="/"><Home /></Route>
<Route path="/category"><Category /></Route>
<Route path="/products"><Products /></Route>
<Route path="/:id">
  <p>This text will render for any route other than '/'</p>
</Route>

If the URL is /products, all the routes that match the location /products are rendered. So, the <Route> with path /:id gets rendered along with the <Products> component. This is by design. However, if this is not the behavior you’re expecting, you should add the <Switch> component to your routes. With <Switch>, only the first child <Route> that matches the location gets rendered:

<Switch>
  <Route exact path="/"><Home /></Route>
  <Route path="/category"><Category /></Route>
  <Route path="/products"><Products /></Route>
  <Route path="/:id">
    <p>This text will render for any route other than those defined above</p>
  </Route>
</Switch>

The :id part of path is used for dynamic routing. It will match anything after the slash and make this value available in the component. We’ll see an example of this at work in the next section.

Now that we know all about the <Route> and <Switch> components, let’s add nested routes to our demo.

Continue reading React Router v5: The Complete Guide on SitePoint.



No comments:

Post a Comment