How to get started with routing in React apps with React Router

Marcin Kwiatkowski /
How to get started with routing in React apps with React Router

React is a popular library for developing single-page applications (SPAs) that can be rendered on the client-side. In a SPA, the end-user who knows Multi Pages expects the following features: Routing is the procedure that keeps the browser URL in sync with the content.

Using declarative routing, you can control the application's traffic information using the expression "the route should look like this..." and place components wherever you have a preference. In reality, it is a third-party library known for its simplicity.

What is React Router?

React itself is focused on building user interfaces, and it lacks a fully integrated routing solution. React Router is React's most popular routing library. It allows you to define paths in the same declarative style as most other libraries. A router allows your application to navigate by changing its browser's URL or browsing history while staying in synch with other elements.

Understanding routes

The matching logic to a component is delegated to the path-to-regexp library. With this behavior, you set which component should be displayed for a specific URL path.

It means that if you have, for example, an "about me" page and the path of this page is "/about-me," you need to assign a component that Reacts will render for that specific path.

What does React Router DOM do?

React Router DOM allows you to implement dynamic routing in web apps. React RouterDOM supports component-based routing according to the app's requirements and the framework. In contrast, the traditional routing architecture provides routing services in a configuration outside of a currently active app. React Router is the best solution for creating React Applications that run in the browser. React router DOM is the quickest way to create routing in React.

Let's dive in!

This tutorial is split out among multiple areas. Our first task is to a create React app and install React Router using npm. Now we'll get down to some basic features of the React Router. Each concept and system for constructing these routes will be discussed along the course.

The full code for the project is published at this GitHub repository. This tutorial presents concepts of using React routing, the basics of React, hooks, and testing.

Prerequisites

I tested the code in Node 14.17.3. I set up the project using Create React App. You will also need a basic knowledge of JavaScript, HTML & CSS add React to understand what is going on here, but if you need to learn React Router, you are familiar with those things.

By The Way: HTML means HyperText Markup Language, so it's not a sexually transmitted disease.

Scaffold the project

As I mentioned before, you'll require the Node installed on your computer for this tutorial. Then you can follow these instructions to set up React project using Create React App.

Changes after this: step: Scaffold the project

Setting up React Router

Now you can install React Router by using npm or yarn. Let's use npm

1$ npm install react-router-dom@6
2

Changes after this: step: Setting up React Router

Cleanin' Out My Closet

Before we go deeper, let's clean some code that CRA generated for us.

Replace index.js with this content:

1import React from 'react';
2import ReactDOM from 'react-dom';
3import App from './App';
4
5ReactDOM.render(
6    <App />,
7    document.getElementById('root')
8);
9

Remove these files: reportWebVitals.js, index.css, logo.svg, App.css, and App.test.js -who needs tests??? But wait, we can add our own tests later, don't worry, I am just trolling you.

Replace App.js with this content:

1import React from "react";
2
3export const App = () => {
4  return <>
5    <h1>Hello, hello, hello</h1>
6  </>
7}
8
9export default App;
10
11
12

Change title and description in public/index.html and remove unnecessary comments. The final result should look like this:

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <meta charset="utf-8" />
5    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6    <meta name="viewport" content="width=device-width, initial-scale=1" />
7    <meta name="theme-color" content="#000000" />
8    <meta
9      name="description"
10      content="React router tutorial"
11    />
12    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
14    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
15    <title>React Router example</title>
16  </head>
17  <body>
18    <noscript>You need to enable JavaScript to run this app.</noscript>
19    <div id="root"></div>
20  </body>
21</html>
22
23

Changes after this: step: Cleanin' Out My Closet

It's not time for styling

Yeah, so let's use React Bootstrap, which provides some components and styles to focus on writing React code. It is a good move, isn't it?

1npm install react-bootstrap bootstrap@5.1.3
2

Import bootstrap styles in the index.js. Just add this line after other imports:

1import 'bootstrap/dist/css/bootstrap.min.css';
2

OK, that all was easy. Let do something less trivial than importing things from npm.

npm developer

Changes after this: step: It's not time for styling

Add Router component

To get React Router working in your App, you need to add a Router. Basically, it means that you need to wrap your app with a top-level router that makes all other React Router components and hooks work. A router is stateful, and it creates history with the initial location and subscribes to the URL.

React Router can subscribe to the URL changes thanks to the History object. Each user action that changes URL is kept in History Stack.

There are three types of those actions: PUSH, POP, and REPLACE.

  • PUSH - a new entry is added to the history stack

  • POP - it happens when a user click Browser's back or forward buttons

  • REPLACE - Replace action is similar to PUSH, but it replaces the current entry in the history stack instead of adding a new one

A location is an object built on top of a window. location object. In this object, you can find information about URL, and in general, it represents where a user is at the time.

There are three types of Routers in react-router-dom:

  • BrowserRouter - recommended for running React Router in a Web browser

  • HashRouter - is used for apps where the URL should not be sent to a server for some reason. It's not recommended to use the Hash router unless you absolutely have to.

  • MemoryRouter - the common case of using MemoryRouter is testing. It stores all information in an array.

OK, so let's wrap our app by BrowserRouter.

index.js:

1import React from 'react';
2import ReactDOM from 'react-dom';
3import { BrowserRouter } from "react-router-dom";
4import App from './App';
5import 'bootstrap/dist/css/bootstrap.min.css';
6
7ReactDOM.render(
8    <BrowserRouter>
9        <App />
10    </BrowserRouter>,
11    document.getElementById('root')
12);
13

Changes after this: step: Add Router component

Add Navigation and links

Update app component (App.js) file with this content:

1import React from "react";
2import { NavLink } from "react-router-dom";
3import { Navbar, Container, Nav } from 'react-bootstrap'
4
5export const App = () => {
6  return <>
7    <Navbar bg="light" expand="lg">
8      <Container>
9        <Navbar.Brand>
10          <NavLink to="/" style={{textDecoration: 'none', color: 'inherit'}}>Your account</NavLink>
11        </Navbar.Brand>
12        <Navbar.Toggle aria-controls="basic-navbar-nav" />
13        <Navbar.Collapse id="basic-navbar-nav">
14          <Nav className="me-auto">
15            <NavLink to="/address" className="nav-link">Address book</NavLink>
16            <NavLink to="/orders" className="nav-link">Orders</NavLink>
17          </Nav>
18        </Navbar.Collapse>
19      </Container>
20    </Navbar>
21    <Container className="mt-3">
22      <h1>Hello, hello, hello</h1>
23    </Container>
24  </>
25}
26
27export default App;
28
29

We imported a NavLink component here from react-router-dom:

1import { NavLink } from "react-router-dom";
2

A NavLink component is a special type of the Link component with an additional feature. It can have an "active" class where the URL is the same as "to property.

A Link React component renders <a> tag with real href property. The difference about the real <a> tag is that React Router will handle navigation to specific locations when you use Link (and NavLink).

So in our app, even we haven't declared any routes yet, the URL is changed without page reloading thanks to React Router.

Besides, we imported there some Bootstrap stuff:

1import { Navbar, Container, Nav } from 'react-bootstrap'
2

The page should look like this:

react router tutorial

Summarizing, we added three links: HomePage: /, Address book: /address, and Orders: /orders.

Changes after this: step: Add navigation and links

Add the first route

React Router is a declarative routing framework that means you configure Routes to use standard React components.

At the end of this step, you now have a react application to find navigation links showing the components for each route.

Let's implement the first route. To do so, we need just to import Route and Routes components in the app and use them in this way:

1import { Routes, BrowserRouter, Route} from "react-router-dom";
2
3(...)
4
5<BrowserRouter>
6    <Routes>
7        <Route path="/" element={<App />} />
8    </Routes>
9</BrowserRouter>
10

So we have a route that handles the "/" path and renders the App, a React component. Basically, nothing has changed in our app so far. Let's implement other routes.

Changes after this step: Add the first route

Add first nested routes and outlet

React Router uses nested routes to provide the most detailed routing details inside child components. Those routes group your routing information directly into components to render other components.

By the finish of this step, you will have various ways of providing info. This is a little additional code, but the routes keep the child's parents in line. Not every project uses a nested route: some prefer an explicit list.

Nested routes allow you to build a complex system of routing. Each route defines a portion of the URL through segments, and a single URL can match multiple routes. Take a look:

Here is our main route:

1/
2

Here is the route for the address book:

1/address
2

And here is the route for address details

1/address/:addressId
2

So the route is built by three routes: / + address/ + :/addressId

Let's implement that scenario. Please, replace <Route path="/" element={<App />} /> by:

1<Route path="/" element={<App />} >
2    <Route path="address" element={<AddressBook />}>
3        <Route path=":addressId" element={<AddressDetails />} />
4    </Route>
5</Route>
6

Hero you go!

Of course, we need to define two new components: AddressBook, and AddressDetails

src/routes/AddressBook/addressBook.js:

1import React from 'react'
2
3export const addressBook = () => {
4    return <p>Address book will be here</p>
5}
6
7export default addressBook;
8
9

src/routes/AddressBook/index.js

1export { default } from './addressBook';
2

Do the same for address details (and do not forget about importing these routes in index.js!)

That should work, but wait. If you go now for the address page, you will see that the address route is rendered, but it looks pretty the same as the index route, but we except that there will be a paragraph: Address book will be here.

path string

To render the content of any child, you need to use the Outlet component that renders the next match in a set of matches.

Please import the Outlet React component to the App component:

1import { NavLink, Outlet } from "react-router-dom";
2

and add it below the <h1>

1<Container className="mt-3">
2  <h1>Hello, hello, hello</h1>
3  <Outlet/>
4</Container>
5

Now the paragraph from the address book component is in place

react application

Changes after this: step: Add first nested routes and Outlet

Add Index routes

Let's go ahead and add some content to the address book. First, add some addresses:

1const addresses = [
2    {
3        id: 1,
4        addressName: 'Polna 1, Wrocław'
5    },
6    {
7        id: 2,
8        addressName: 'Wrocławska 2, Warszawa'
9    }
10];
11

Then, render navigation with addresses:

1const navLinks = addresses.map(address => {
2    return <ListGroupItem key={address.id}>
3        <NavLink to={`/address/${address.id}`} key={address.id}>{address.addressName}</NavLink>
4    </ListGroupItem>
5});
6
7const shouldDisplayNav = navLinks && navLinks.length ? <ListGroup>{navLinks}</ListGroup> : <p>There are no addresses.</p>;
8

Return all stuff with a nice layout:

1return addresses ? <Row>
2    <Col sm="3">
3        {shouldDisplayNav}
4    </Col>
5    <Col sm="9">
6        <Outlet/>
7    </Col>
8</Row> : <Row>
9    <p>There are no addresses.</p>
10</Row>
11

Do not forget about imports:

1import { NavLink, Outlet } from "react-router-dom";
2import { ListGroupItem, ListGroup, Col, Row } from "react-bootstrap";
3

The page should look like this:

react router - create nested routes

On the right of navigation, there is a space for address details, but initially then is empty space. When you click on an address in navigation, you can see address details.

There is a way to add some improvements! Let's add a paragraph that says: "Please select an address." To do so that you can use another pretty cool feature of React Router called: Index route

Add this code to index.js to AddressBook route component:

1<Route
2    index
3    element={
4        <p>Select an address.</p>
5    }
6/>
7

Now, when you go to the address book, you can see "Select an address" text by default:

react router - create nested routes

Let's do something similar for the home route:

1<Route
2    index
3    element={
4        <>
5            <h2>Welcome in your account.</h2>
6            <p>Please use the navigation above to see Address book or your orders.</p>
7        </>
8    }
9/>
10

Remove this code from App.js

1<h1>Hello, hello, hello</h1>
2

The homepage looks like this now:

how yo build dynamic routes with react router

Changes after this: step: Add index routes

Use URL params

We have already defined a route for Address Details that receives addressId param:

1<Route path=":addressId" element={<AddressDetails />} />
2

When you click on addresses, the URL is changing:

1http://localhost:3000/address/1
2http://localhost:3000/address/2
3

"1" and "2" in this case are addresses ID. The question is: how do we handle those params in the AddressDetails React component?

useParams hook

React Router provides a useParams hook that allows you to handle URL params. Take a look:

1import React from 'react'
2import { useParams } from "react-router-dom";
3
4export const AddressDetails = () => {
5    const { addressId } = useParams();
6    return <p>Address details for {addressId} will be here</p>
7}
8
9export default AddressDetails;
10
11

Now, addressId is handled by the AddressDetails component:

how yo build dynamic routes with react router

Changes after this step: Use URL Params

Use search params

React Routes provides a useSearchParams hook that allows you to read and modify a query part of a URL (q=). Let's use it to add some filtering to the App.

First import useSearchParams hook in the AddressBook React component ad get searchParams, and setSearchParams from it:

1import { useSearchParams } from "react-router-dom";
2
3// below in the compoonent body:
4
5const [searchParams, setSearchParams] = useSearchParams();
6

Second, add a search form. To do so, add this code at the beginning of the return function:

1<Col sm="12">
2    <nav>
3        <InputGroup size="sm" className="mb-3">
4            <InputGroup.Text id="address-search">Search for an address</InputGroup.Text>
5            <FormControl aria-label="Search for an address"
6                         aria-describedby="address-search"
7                         value={searchParams.get("filter") || ""}
8                         onChange={event => {
9                             const filter = event.target.value;
10                             if (filter) {
11                                 setSearchParams({ filter });
12                             } else {
13                                 setSearchParams({});
14                             }
15                         }} />
16        </InputGroup>
17    </nav>
18</Col>
19

A function bound on the onCahnge event sets the current input value to the URL query param.

Third, let's read the query param and filter addresses by it:

1const navLinks = addresses
2    .filter(address => {
3        const filter = searchParams.get('filter');
4        if (!filter) return true;
5
6        let name = address.addressName.toLowerCase();
7
8        return name.startsWith(filter.toLowerCase());
9    })
10    .map(address => {
11        return <ListGroupItem key={address.id}>
12            <NavLink to={`/address/${address.id}`} key={address.id}>{address.addressName}</NavLink>
13        </ListGroupItem>
14});
15

In the previous step, we named a param by word: filter, and now we can read that value by using this: searchParams.get('filter');

Take a look at the filtering in action:

react router - path parameters - search params

Changes after this step: Use search params

Handle no matching route

The last thing I want to show you is the no-match route. It's a case when the user goes to a route that does not exist, for example,/blablabla

To handle that, add this route component definition at the end of your route components definitions:

1<Route
2    path="*"
3    element={
4        <main>
5            <p style={{padding: '30px', textAlign: 'center'}}>There's nothing here!</p>
6        </main>
7    }
8/>
9

That code handles all routers not handled by other defined routes components. On the other hand, if no routes match, those elements will be rendered. Of course, you can use the react component as well.

Changes after this step: Handle no matching route

An additional thing: protected Routes

A protected route is used to ensure only logged-in users can use some places on your site. Typically we create e a secure route component for someone in the system to use /admin when they attempt to connect. However, some aspects of React Router must first be covered.

Basically, you can create a special React component that will check if a user can go to a protected route or not.

Working Demo

Here you can see the demo of the application we developed with react-router:

react-router-tutorial-omega.vercel.app

Source code

Here you can find the source code for this tutorial: https://github.com/Frodigo/react-router-tutorial

Here are the commits for each step:

  1. Scaffold the project

  2. Setting up React Router

  3. Cleanin' Out My Closet

  4. It's not time for styling

  5. Add Router component

  6. Add navigation and links

  7. Add the first route

  8. Add first nested routes and Outlet

  9. Add index routes

  10. Use URL Params

  11. Use search params

  12. Handle no matching route

Summary

React Router lets you handle all the routes in a React application. You can use it for a web app or even for React native app.

The router is one of the main React Router components, and for web apps, there is a BrowserRouter react component, a router implementation that uses HTML5 History API.

React Router provides other essential components are Routes, Route, Link, and NavLink.

Besides, there are a few hooks like useParams, useSearchParams, and useNavigate.

So react-router package provides just components and just hooks, and those all together allow you to create complex routing systems easily.

Share this article with your friends!

Each share supports and motivates me to create new content. Thank you!

About the author

Marcin Kwiatkowski

Frontend developer, Certified Magento Full Stack Developer, writer, based in Poland. He has eight years of professional Software engineering experience. Privately, husband and father. He likes reading books, drawing, and walking through mountains.

Read more