Telerik blogs

React Router allows us to route to different “pages” even on a single-page app. Learn how to implement it and style its links.

Most frontend apps have more than one page of content. In a single-page app, we add multiple pages of data by routing URLs to show different content.

React does not have a routing library built in. Therefore, we have to add one ourselves.

React Router is a popular routing library for React apps. In this article, we will look at how to use React Router v6 to add routing capabilities to our React apps.

Basic Usage

To use it, we install the react-router-dom package.

To do this, we run:

npm i react-router-dom

Then we write:

index.js

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

App.js

import * as React from "react";
import { createBrowserRouter, RouterProvider, Link } from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        <Link to="about">About Us</Link>
        <div>Hello World</div>
      </div>
    ),
  },
  {
    path: "about",
    element: <div>About</div>,
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

We call createBrowserRouter to create a router object. We call it with an array of routes.

The path is mapped to the element being rendered. Therefore, when we go to the path, we see the content in element rendered.

The Link component is used to render a link. And the to prop is set to the route URL we want to go in the app.

We use RouterProvider with the router prop set to router to render the routes when we go to the URLs listed in the path properties.

We can also add nested routes easily. To do this, we can nest the routes in the children array.

For example, we write:

App.js

import * as React from "react";
import {
  createBrowserRouter,
  RouterProvider,
  redirect,
  Outlet,
  useLoaderData,
} from "react-router-dom";

const Dashboard = () => {
  const data = useLoaderData();
  return <div>dashboard {JSON.stringify(data)}</div>;
};

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        hello <Outlet />
      </div>
    ),
    children: [
      {
        path: "contact",
        element: <div>contact</div>,
      },
      {
        path: "dashboard",
        element: <Dashboard />,
        loader: ({ request }) =>
          fetch("https://yesno.wtf/api", {
            signal: request.signal,
          }),
      },
      {
        element: (
          <div>
            auth <Outlet />
          </div>
        ),
        children: [
          {
            path: "login",
            element: <div>login</div>,
          },
          {
            path: "logout",
            action: <div>logout</div>,
            loader: () => redirect("/login"),
          },
        ],
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

to add nested routes by putting them in the children array. We use the Outlet component to render nested route contents.

loader is set to a function that returns a promise.

request.signal is an abort signal object that we can use to cancel the request if we want to.

To get the value returned by the promise, we use the useLoaderData hook. In Dashboard, we just call it to get the returned data.

We add another nested route in the last children array with the login and logout routes. In the logout route, we redirect to the login route with the redirect function.

Route Placeholder Segments

We can add route placeholder segments by starting them with semicolons.

For instance, we write:

import * as React from "react";
import {
  createBrowserRouter,
  RouterProvider,
  Outlet,
  useLoaderData,
  useParams,
} from "react-router-dom";

const Dashboard = () => {
  const data = useLoaderData();
  const params = useParams();
  return (
    <div>
      dashboard {JSON.stringify(data)} {JSON.stringify(params)}
    </div>
  );
};

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        hello <Outlet />
      </div>
    ),
    children: [
      {
        path: "dashboard/:parentId/child/:childId",
        element: <Dashboard />,
        loader: ({ params }) => {
          return params;
        },
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

to add the :parentId and :childId route placeholders.

Then we call useParams in the Dashboard component to get an object with the parentId and childId properties which have the route parameters.

The loader function also has a params property in the parameter object. And we can do things with it or return it. If it’s returned, we can get the values in useLoaderData as we do. And the result is the same value as what is returned with useParams.

Query Strings

We can get and set query strings with the useSearchParams hook.

To do this, we write:

import * as React from "react";
import {
  createBrowserRouter,
  RouterProvider,
  Outlet,
  useSearchParams,
} from "react-router-dom";

const Dashboard = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  console.log(searchParams.toString());

  return (
    <button onClick={() => setSearchParams({ foo: 1, bar: 2 })}>go</button>
  );
};

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <div>
        hello <Outlet />
      </div>
    ),
    children: [
      {
        path: "dashboard",
        element: <Dashboard />,
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

to add the useSearchParams hook to the Dashboard component.

In it, we call useSearchParams to return an array with the getter and setter function.

We get the query string value with searchParams and we call setSearchParams to set the query string.

searchParams is a regular URLSearchParams object built into the browser so we can call toString to get the query string.

We call setSearchParams with an object with the query parameter keys and values to redirect to the URL with the query string.

React Router v6 comes with the NavLink component that lets us style links easily depending on whether we are on the page that it links to or not.

For instance, we can use it by writing:

import * as React from "react";
import {
  createBrowserRouter,
  RouterProvider,
  Outlet,
  NavLink,
} from "react-router-dom";

const Root = () => {
  return (
    <div>
      <NavLink
        style={({ isActive }) => {
          return {
            color: isActive ? "red" : "inherit",
          };
        }}
        className={({ isActive, isPending }) => {
          return isActive ? "active" : isPending ? "pending" : "";
        }}
        to="/"
      >
        home
      </NavLink>
      <NavLink
        style={({ isActive }) => {
          return {
            color: isActive ? "red" : "inherit",
          };
        }}
        className={({ isActive, isPending }) => {
          return isActive ? "active" : isPending ? "pending" : "";
        }}
        to="dashboard"
      >
        dashboard
      </NavLink>
      <NavLink
        style={({ isActive }) => {
          return {
            color: isActive ? "red" : "inherit",
          };
        }}
        className={({ isActive, isPending }) => {
          return isActive ? "active" : isPending ? "pending" : "";
        }}
        to="contact"
      >
        contact
      </NavLink>
      <Outlet />
    </div>
  );
};

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    children: [
      {
        path: "/",
        element: <div>home</div>,
      },
      {
        path: "dashboard",
        element: <div>dashboard</div>,
      },
      {
        path: "contact",
        element: <div>contact</div>,
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

to add the Root component.

In it, we add NavLink components to add links that we can style depending on whether we are on the page the link links to or not.

We set the style prop to a function that takes an object with the isActive property.

If isActive is true, then we are on the page that the link links to. And we set the link color to red in that case. Otherwise, we set it to the default inherited color.

The isPending property is also available in the object parameter so we can check whether navigation to the route is pending.

Likewise, we can set the className property to a function that takes the same object. And we can return class name strings for the link depending on the state of navigation as we do with the function we set as the value of the style prop.

Now we can see that the link turns red when we are on the page that the link links to.

Conclusion

React Router is a simple-to-use routing library that we can use to create React apps with multiple pages. It has all the functionality like query string and URL parameter handling built in. We can also use it to do things when a route is loading.

And it also comes with an easy way to render links to different routes and style them depending on whether we’re already on the linked page.


About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.

Related Posts

Comments

Comments are disabled in preview mode.