Routing with React Router v6.4

Discover the enhanced routing capabilities and data fetching simplifications introduced in React Router v6.4.

ยท

6 min read

Routing with React Router v6.4

React Router, a fundamental tool for managing navigation in React applications, has released version 6.4, which includes a number of enhancements and tweaks.
With the recent release of React Router, the framework has undergone a significant transformation, improving the user experience and streamlining development processes.

We'll talk about the Benefits and Enhancements of v6.4 later in the blog.

Routers

In v6.4, 4 new routers were introduced.

  • createBrowserRouter

  • createMemoryRouter

  • createHashRouter

  • createStaticRouter

In this article, we'll study about the createBrowserRouter and how it differs from the traditional<BrowserRouter>

Only these new routers support the new data APIs.

There are a ton of new data APIs:

  • route.lazy

  • route.loader

  • route.shouldRevalidate

  • route.handle

  • useActionData

  • useAsyncError

  • useAsyncValue

  • useLoaderData

  • useMatches

  • useNavigation

  • useRevalidator

  • useRouteError

  • useRouteLoaderData

And many more...!

In this article, we'll briefly study about route.loader & useLoaderData

So, if you want to use the above data apis, which are by the way quite helpful, then you need to migrate from <BrowserRouter> to one of the new routers.

Picking a Router

The React Router Official Docs suggests that:

  • createBrowserRouter: All web projects use createBrowserRouter. It makes use of full URLs and is better for SEO, and of course, it supports the new data apis

  • createMemoryRouter : createMemoryRouter creates a router that manages its own history stack in memory. This simplifies the process of testing components that use React Router by allowing you to isolate your components from the browser's history stack, making it easier to test them in a controlled environment.

  • createHashRouter : If for some reason you can't use the full URL, createHashRouter can be a suitable fit.

  • createStaticRouter : createStaticRouter creates a router for server-side rendering (SSR). SSR is a technique for rendering React components on the server, rather than on the client. This can improve the performance of your application, especially for initial page loads.

Let's dive straight into the code!

Basic Routing Setup

  1. In Previous versions using browserRouter App.js ๐Ÿ‘‡
import * as React from "react";
import { BrowserRouter, Route, Link, Routes } from "react-router-dom";

export default function App() {
  return (
    <BrowserRouter>
      <main>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/user">User</Link>
            </li>
            <li>
              <Link to="/contact">Contact</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/user" element={<User />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </main>
    </BrowserRouter>
  );
}
  1. In the Latest v6.4 using createBrowserRouter

Method 1

App.js ๐Ÿ‘‡

import {
  createBrowserRouter,
  RouterProvider,
  Route,
  createRoutesFromElements,
} from "react-router-dom";
import "./App.css";
import { RootLayout } from "./components/RootLayout";

export default function App() {
  const router = createBrowserRouter(
    createRoutesFromElements(
      <Route path="/" element={<RootLayout />}>
        <Route index element={<Home />} />
        <Route path="/user" element={<User />} />
        <Route path="/contact" element={<Contact />} />
      </Route>
    )
  );

  return <RouterProvider router={router} />;
}

RootLayout.jsx ๐Ÿ‘‡

import { Outlet , Link} from "react-router-dom";

export const RootLayout = () => {
  return (
  <main>
    <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/user">User</Link>
          </li>
          <li>
            <Link to="/contact">Contact</Link>
          </li>
        </ul>
      </nav>

    {/* Components will be rendered here */}
    <Outlet />

    </main>)
};

Let's understand the above code setup

  • App.js : createBrowserRouter function and wraps the application with RouterProvider to provide routing context.

  • RootLayout.jsx : This file defines the RootLayout component, which acts as the main layout for the application. It renders the common elements, such as the header and navigation bar, and provides an Outlet component to dynamically render the child routes.

  • createRoutesFromElements : This function from React Router v6.4 is used to create a nested routing configuration. It allows you to define nested routes within a parent route, enabling a hierarchical routing structure.

There's an alternate way of defining createBrowserRouter too:

Method 2:

export default function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: [
        {
          path: "/",
          element: <Home />,
        },
        {
          path: "user",
          element: <User />,
        },
        {
          path: "contact",
          element: <Contact />,
        },
      ],
    },
  ]);

  return <RouterProvider router={router} />;
}

Both methods of defining createBrowserRouter are valid. The choice between them depends on your specific needs and preferences.

Considerations :

  • Conciseness: Method 2 is generally more concise for smaller routing configurations.

  • Readability: Both methods are relatively readable, but method 1 may be slightly more readable for complex routing structures due to its declarative syntax.

  • Maintenance: Both methods are generally maintainable, but method 1 may be easier to maintain for complex routing structures due to its declarative syntax and clear organization.

Now it's time to learn more about the Benefits & Enhancements!

Benefits & Enhancements:

  1. Data APIs Integration: React Router v6.4 introduces data APIs that allow you to manage data fetching, loading, and synchronization within your routing logic. These APIs provide a convenient way to handle data dependencies and ensure that your routes have the data they need when they render.

  2. Declarative API: React Router v6.4 adopts a declarative API, making it easier to define and manage routing configurations. Instead of using imperative Route components, you can now define routes using a declarative syntax, enhancing readability and maintainability.

  3. Nested Routes: The new API supports nested routes and route groups, enabling you to organize your routing structure more effectively. Nested routes allow you to create hierarchical routing structures, while route groups let you group related routes and share configuration options.

Now, its time to study the new Data API: loader & useDataLoader

The useDataLoader hook simplifies data fetching in React Router v6.4 by handling data loading and synchronization directly within the routing configuration.

This means you don't need to manually fetch data in individual components, reducing the need for useEffect and useState hooks.

Let's take a look at the code

User.jsx :


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

export const User = () => {
  const userData = useLoaderData();

  return (
    <div>
      <h1>User</h1>
      <p>Name: {userData.name}</p>
      <p>Email: {userData.email}</p>
    </div>
  );
};

export const userDataLoader = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  const data = await response.json();
  return data;
};

App.js:

export default function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: [
        {
          path: "/",
          element: <Home />,
        },
        {
          path: "user",
          element: <User />,
          loader: userDataLoader,
        },
        {
          path: "contact",
          element: <Contact />,
        },
      ],
    },
  ]);

  return <RouterProvider router={router} />;
}

This is how data is fetched and kept ready when we hit a route This helps in:

  1. Eliminating Manual Data Fetching: Instead of using useEffect to fetch data in components, useDataLoader takes care of it automatically.

  2. Avoiding Re-renders Caused by Data Fetching: By fetching data directly within the routing configuration, useDataLoader ensures that components are not re-rendered every time data changes. This is because the data is loaded and made available to components through the routing context, not through the component state

  3. Simplifying State Management: Since useDataLoader handles data fetching and synchronization, you don't need to manage data state using useState hooks in components that rely on this data.

That's it for this blog.

Summary of Covered Topics

1. Routing Setup Differences:

  • React Router v6.4 introduces a more declarative and composable approach to routing compared to previous versions.

  • The createBrowserRouter function provides a more explicit and modular way to define routing configurations.

2. Data APIs and useDataLoader:

  • React Router v6.4 introduces data APIs that integrate data fetching and synchronization within routing logic.

  • These APIs only work with the new routers.

3. Benefits of useDataLoader:

  • Eliminates manual data fetching in components.

  • Reduces re-renders caused by data changes & simplifies data state management.

  • Promotes a declarative and centralized approach to data management.

  • Provides data to the route element before it renders.

I hope you enjoyed reading this blog! If you found this blog informative and helpful, please give it a thumbs up and share it with your fellow developers. Feel free to comment if you would like to see a part 2 of this blog. I am all ears to your feedback.