If you have just started with React, you are probably still wrapping your head around the whole Single Page Application concept.

Traditionally routing works like this: let's say you type in /contact in the URL. The browser will make a GET request to the server, and the server will return an HTML page as the response.

But, with the new Single Page Application paradigm, all the URL requests are served using the client-side code.

Applying this in the context of React, each page will be a React component. React-Router matches the URL and loads up the component for that particular page.

Everything happens so fast, and seamlessly, that the user gets a native app-like experience on the browser. There is no flashy blank page in between route transitions.

In this article, you'll learn how to use React-Router and its components to create a Single Page Application. So open up your favorite text editor, and let's get started.

Setup the project

Create a new React project by running the following command.

yarn create react-app react-router-demo

I'll be using yarn to install the dependencies, but you can use npm as well.

Next, let's install react-router-dom.

yarn add react-router-dom

For styling the components, I'm going to use the Bulma CSS framework. So let's add that as well.

yarn add bulma

Next, import bulma.min.css in the index.js file and clean up all the boilerplate code from the App.js file.

import "bulma/css/bulma.min.css";

Now that you have the project set up let's start by creating a few page components.

Creating the Page Components

Create a pages directory inside the src folder where we will park all the page components.

For this demo, create three pages - Home, About, and Profile.

Paste the following inside the Home and About components.

// pages/Home.js

import React from "react";

const Home = () => (
  <div>
    <h1 className="title is-1">This is the Home Page</h1>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras gravida,
      risus at dapibus aliquet, elit quam scelerisque tortor, nec accumsan eros
      nulla interdum justo. Pellentesque dignissim, sapien et congue rutrum,
      lorem tortor dapibus turpis, sit amet vestibulum eros mi et odio.
    </p>
  </div>
);

export default Home;
// pages/About.js

import React from "react";

const About = () => (
  <div>
    <h1 className="title is-1">This is the About Page</h1>
    <p>
      Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
      inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus
      et ultrices posuere cubilia curae; Duis consequat nulla ac ex consequat,
      in efficitur arcu congue. Nam fermentum commodo egestas.
    </p>
  </div>
);

export default About;

We will create the Profile page later on in the article.

Create the Navbar Component

Let's start by creating the navigation bar for our app. This component will make use of the <NavLink /> component from react-router-dom.

Create a directory called "components" inside the src folder.

// components/Navbar.js

import React, { useState } from "react";
import { NavLink } from "react-router-dom";

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return ( 
  	<nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
      	{/* ... */}
      </div>
    </nav>
  );
 };
 
 export default Navbar;

The isOpen state variable will be used to trigger the menu on mobile or tablet devices.

So let's add the hamburger menu.

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return ( 
  	<nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
      <div className="navbar-brand">
          <a
            role="button"
            className={`navbar-burger burger ${isOpen && "is-active"}`}
            aria-label="menu"
            aria-expanded="false"
            onClick={() => setOpen(!isOpen)}
          >
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </a>
        </div>
      	{/* ... */}
      </div>
    </nav>
  );
 };

To add the link in the menu, use the <NavLink /> component by react-router-dom.

The NavLink component provides a declarative way to navigate around the application. It is similar to the Link component, except it can apply an active style to the link if it is active.

To specify which route to navigate to, use the to prop and pass the path name.
The activeClassName prop will add an active class to the link if it's currently active.

<NavLink
    className="navbar-item"
    activeClassName="is-active"
    to="/"
    exact
>
	Home
</NavLink>

On the browser, the NavLink component is rendered as an <a> tag with an href attribute value that was passed in the to prop.

Also, here you need to specify the exact prop so that it is matched precisely with the URL.

Add all the links and finish up the Navbar component.

import React, { useState } from "react";
import { NavLink } from "react-router-dom";

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return (
    <nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
        <div className="navbar-brand">
          <a
            role="button"
            className={`navbar-burger burger ${isOpen && "is-active"}`}
            aria-label="menu"
            aria-expanded="false"
            onClick={() => setOpen(!isOpen)}
          >
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </a>
        </div>

        <div className={`navbar-menu ${isOpen && "is-active"}`}>
          <div className="navbar-start">
            <NavLink className="navbar-item" activeClassName="is-active" to="/">
              Home
            </NavLink>

            <NavLink
              className="navbar-item"
              activeClassName="is-active"
              to="/about"
            >
              About
            </NavLink>

            <NavLink
              className="navbar-item"
              activeClassName="is-active"
              to="/profile"
            >
              Profile
            </NavLink>
          </div>

          <div className="navbar-end">
            <div className="navbar-item">
              <div className="buttons">
                <a className="button is-white">Log in</a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </nav>
  );
};

export default Navbar;

If you notice here, I've added a Login button. We will come back to the Navbar component again when we discuss protected routes later on in the guide.

Rendering the pages

Now that the Navbar component is set up let's add that to the page and start with rendering the pages.

Since the navigation bar is a common component across all the pages, instead of calling the component in each page component, it will be a better approach to render the Navbar in a common layout.

// App.js

function App() {
  return (
    <>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        {/* Render the page here */}
      </div>
    </>
  );
}

Now, add the page components inside of the container.

// App.js

function App() {
  return (
    <>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        <Home />
      	<About />
      </div>
    </>
  );
}

If you check out the results now, you'll notice that both the Home and the About page component gets rendered onto the page. That's because we haven't added any routing logic yet.

You need to import the BrowserRouter component from React Router to add the ability to route the components. All you need to do is wrap all the page components inside of the BrowserRouter component. This will enable all the page components to have the routing logic. Perfect!

But again, nothing's going to change with the results – you are still going to see both the pages rendered. You need to render the page component only if the URL matches a particular path. That's where the Route component from React Router comes into play.

The Router component has a path prop that accepts the page's path, and the page component should be wrapped with the Router, as shown below.

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

So let's do the same for the Home component.

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

The exact prop above tells the Router component to match the path exactly. If you don't add the exact prop on the / path, it will match with all the routes starting with a / including /about.

If you go check out the results now, you'll still see both the components rendered. But, if you go to /about, you'll notice that only the About component gets rendered. You see this behavior because the router keeps matching the URL with the routes even after it had matched a route already.

We need to tell the router to stop matching further once it matches a route. This is done using the Switch component from React Router.

function App() {
  return (
    <BrowserRouter>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

There you go! You have successfully configured routing in your React app.

Protected Routes and Redirect

When working on Real-world applications, you will have some routes behind an authentication system. You are going to have routes or pages that can only be accessed by a logged-in user. In this section, you'll learn how to go about implementing such routes.

Please note that I'm not going to create any login form or have any back-end service authenticate the user. In a real application, you wouldn't implement authentication the way demonstrated here.

Let's create the Profile page component that should only be accessed by the authenticated user.

// pages/Profile.js

import { useParams } from "react-router-dom";

const Profile = () => {
  const { name } = useParams();
  return (
    <div>
      <h1 className="title is-1">This is the Profile Page</h1>
      <article className="message is-dark" style={{ marginTop: 40 }}>
        <div className="message-header">
          <p>{name}</p>
        </div>
        <div className="message-body">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.{" "}
          <strong>Pellentesque risus mi</strong>, tempus quis placerat ut, porta
          nec nulla. Vestibulum rhoncus ac ex sit amet fringilla. Nullam gravida
          purus diam, et dictum <a>felis venenatis</a> efficitur. Aenean ac{" "}
          <em>eleifend lacus</em>, in mollis lectus. Donec sodales, arcu et
          sollicitudin porttitor, tortor urna tempor ligula, id porttitor mi
          magna a neque. Donec dui urna, vehicula et sem eget, facilisis sodales
          sem.
        </div>
      </article>
    </div>
  );
};

We will grab the user's name from the URL using route parameters.

Add the Profile route into the router.

<Route path="/profile/:name">
  <Profile />
</Route>

Currently the profile page can be accessed directly. So to make it an authenticated route, create a Higher-Order component (HOC) to wrap the authentication logic.

const withAuth = (Component) => {
  const AuthRoute = () => {
    const isAuth = !!localStorage.getItem("token");
    // ...
  };

  return AuthRoute;
};

To determine if a user is authenticated or not, grab the authentication token that is stored in the browser when the user logs in. If the user is not authenticated, redirect the user to the Home page. The Redirect component from React Router can be used to redirect the user to another path.

const withAuth = (Component) => {
  const AuthRoute = () => {
    const isAuth = !!localStorage.getItem("token");
    if (isAuth) {
      return <Component />;
    } else {
      return <Redirect to="/" />;
    }
  };

  return AuthRoute;
};

You can also pass in other user information like name and user ID using props to the wrapped component.

Next, use the withAuth HOC in the Profile component.

import withAuth from "../components/withAuth";

const Profile = () => {
 // ...
}

export default withAuth(Profile);

Now, if you try to visit /profile/JohnDoe, you will get redirected to the Home page. That's because the authentication token is not yet set in your browser storage.

Alright, so, let's return to the Navbar component and add the login and logout functionalities. When the user is authenticated, show the Logout button and when the user is not logged in show the Login button.

// components/Navbar.js

const Navbar = () => {
	// ...
    return (
    	<nav
          className="navbar is-primary"
          role="navigation"
          aria-label="main navigation"
        >
        <div className="container">
        	{/* ... */}
            <div className="navbar-end">
            <div className="navbar-item">
              <div className="buttons">
                {!isAuth ? (
                  <button className="button is-white" onClick={loginUser}>
                    Log in
                  </button>
                ) : (
                  <button className="button is-black" onClick={logoutUser}>
                    Log out
                  </button>
                )}
              </div>
            </div>
          </div>
        </div>
        </nav>
    );
}

When the user clicks on the login button, set a dummy token in the local storage, and redirect the user to the profile page.

But we cannot use the Redirect component in this case – we need to redirect the user programmatically. Sensitive tokens used for authentication are usually stored in cookies for security reasons.

React Router has a withRouter HOC that injects the history object in the props of the component to leverage the History API. It also passes the updated match and location props to the wrapped component.

// components/Navbar.js

import { NavLink, withRouter } from "react-router-dom";

const Navbar = ({ history }) => { 
  const isAuth = !!localStorage.getItem("token");

  const loginUser = () => {
    localStorage.setItem("token", "some-login-token");
    history.push("/profile/Vijit");
  };

  const logoutUser = () => {
    localStorage.removeItem("token");
    history.push("/");
  };
  
  return ( 
   {/* ... */}
  );
};

export default withRouter(Navbar);

And voilà! That's it. You have conquered the land of authenticated routes as well.

Check out the live demo here and the complete code in this repo for your reference.

Conclusion

I hope by now you have a fair idea of how client-side routing works in general and how to implement routing in React using the React Router library.

In this guide, you learned about the vital components in React Router like Route, withRouter, Link, and so on, along with some advanced concepts like authenticated routes, that are required to build an application.

Do check out the React Router docs to get a more detailed overview of each of the components.