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 the 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 in an application 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.
- The dynamically generated nested views should preferably have a URL of their own too. For example,
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" component={About} />
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 used to routing in React.
A note before getting started. 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. If your requirements are limited to routers for navigation, you could implement a custom router from scratch without much hassle. However, understanding how the basics of React Router will give you better insights into how a router should work.
Overview
This tutorial is divided into different sections. First, we’ll be setting up React and React Router using npm. Then we’ll jump right into 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. Once you’re inside a particular demo directory, run npm install
to install the dependencies. To serve the application on a development server, run npm start
and head over to http://localhost:3000/
to see the demo in action.
Let’s get started!
Setting up React Router
I assume you already have a development environment up and running. If not, head over to “Getting Started with React and JSX”. Alternatively, you can use Create React App to generate the files required for creating a basic React project. This is the default directory structure generated by Create React App:
react-router-demo
├── .gitignore
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── README.md
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ └── registerServiceWorker.js
└── yarn.lock
The React Router library comprises three packages: react-router
, react-router-dom
, and react-router-native
. react-router
is the core package for the 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 on a mobile app development environment using React Native.
Use npm to install react-router-dom
:
npm install --save react-router-dom
React Router Basics
Here’s an example of how our routes will look:
<Router>/* App component */
class App extends React.Component {
render() {
return (
<div>
<nav className="navbar navbar-light">
<ul className="nav navbar-nav">
/* Link components are used for linking to other views */
<li>
<Link to="/">Homes</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="/" component={Home} />
<Route path="/category" component={Category} />
<Route path="/products" component={Products} />
</div>
);
}
}
<Route exact path="/" component={Home} />
<Route path="/category" component={Category} />
<Route path="/login" component={Login} />
<Route path="/products" component={Products} />
</Router>
Router
You need a router component and several route components to set up a basic route as exemplified above. Since we’re building a browser-based application, we can use two types of routers from the React Router API:
<BrowserRouter>
<HashRouter>
The primary difference between them is evident in the URLs that they create:
// <BrowserRouter>
http://example.com/about
// <HashRouter>
http://example.com/#/about
The <BrowserRouter>
is more popular amongst the two because it uses the HTML5 History API to keep track of your router history. The <HashRouter>
, on the other hand, uses the hash portion of the URL (window.location.hash
) to remember things. If you intend to support legacy browsers, you should stick with <HashRouter>
.
Wrap the <BrowserRouter>
component around the App component.
index.js
/* Import statements */
import React from "react";
import ReactDOM from "react-dom";
/* App is the entry point to the React code.*/
import App from "./App";
/* import BrowserRouter from 'react-router-dom' */
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
Note: A router component can only have a single child element. The child element can be an HTML element — such as div — or a react component.
For the React Router to work, you need to import the relevant API from the react-router-dom
library. Here I’ve imported the BrowserRouter
into index.js
. I’ve also imported the App
component from App.js
. App.js
, as you might have guessed, is the entry point to React components.
The above code creates an instance of history for our entire App component. Let me formally introduce you to history.
history
history
is a JavaScript library that lets you easily manage session history anywhere JavaScript runs.history
provides a minimal API that lets you manage the history stack, navigate, confirm navigation, 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. history.push()
is invoked when you click on a <Link>
component, and history.replace()
is called when you use <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.
Links and Routes
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 pathname is matched with 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 browser 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 browser refresh.
We’ve covered everything you need to know to create a basic router. Let’s build one.
Demo 1: Basic Routing
src/App.js
/* Import statements */
import React, { Component } from "react";
import { Link, Route, Switch } from "react-router-dom";
/* Home component */
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
/* Category component */
const Category = () => (
<div>
<h2>Category</h2>
</div>
);
/* Products component */
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="/">Homes</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="/" component={Home} />
<Route path="/category" component={Category} />
<Route path="/products" component={Products} />
</div>
);
}
We’ve declared the components for Home, Category and Products inside App.js
. Although this is okay for now, when the 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. The component that should be rendered is passed in as a second prop.
Here /
matches both /
and /category
. Therefore, both the routes are matched and rendered. How do we avoid that? You should pass the exact= {true}
props to the router with path='/'
:
<Route exact={true} path="/" component={Home} />
If you want a route to be rendered only if the paths are exactly the same, you should use the exact props.
Nested Routing
To create nested routes, we need to have a better understanding of how <Route>
works. Let’s do that.
<Route>
has three props that you can you use to define what gets rendered:
- component. We’ve already seen this in action. When the URL is matched, the router creates a React element from the given component using
React.createElement
. - render. This is handy for inline rendering. The render prop expects a function that returns an element when the location matches the route’s path.
- children. The children prop is similar to render in that it expects a function that returns a React element. However, children gets rendered regardless of whether the path is matched with the location or not.
Path and match
The path 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 and we call it the match object. The match object carries 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>
smatch.path
. A string that returns the route’s path string — that is,<Route path="">
. We’ll be using this to build nested<Route>
s.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.
Now that we know all about <Route>
s, let’s build a router with nested routes.
Switch Component
Before we head for 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="/" component={Home}/>
<Route path="/products" component={Products}/>
<Route path="/category" component={Category}/>
<Route path="/:id" render = {()=> (<p> I want this text to show up for all routes other than '/', '/products' and '/category' </p>)}/>
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.
Continue reading React Router v5: The Complete Guide on SitePoint.
No comments:
Post a Comment