React Router Data Fetching Made Easy: A Step-by-Step Guide
Tags: javascript, react, tutorial, webdev
Published: 2022-12-27 20:37:45

## Introduction

In the most recent versions of React Router we were given a set of very interesting primitives and many of them are related to data fetching.

In the past, we always made http requests after the component was mounted and dealt with the loading and error status depending on the result of that request. That is, we only had two aspects into account, **what** and **how** we were going to fetch it. Now, we can think about **when** we want to fetch the data and the approach we can do is at the route level.

Basically, as soon as the user navigates to a new route, data loading and rendering is done in parallel.

## Assumed knowledge

The following would be helpful to have:

- Basic knowledge of React
- Basic knowledge of React Router

## Getting Started

### Project Setup

Run the following command in a terminal:

```bash
yarn create vite router-defer --template react
cd router-defer
```

Now we can install the necessary dependencies:

```bash
yarn add react-router-dom
```

### Build the Components

The first step is to build the component that will contain the app's `<Outlet />`, still in this component we will create a function called `randomIntFromInterval()` that will generate a random number within an interval.

```js
// @src/components/Layout.jsx
import { useCallback } from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";

const Layout = () => {
  const navigate = useNavigate();

  const randomIntFromInterval = useCallback((min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }, []);

  const handlePostavigation = useCallback(() => {
    navigate(`/post/${randomIntFromInterval(6, 12)}`);
  }, [randomIntFromInterval]);

  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <button onClick={handlePostavigation}>Random Post</button>
      </nav>

      <Outlet />
    </div>
  );
};

export default Layout;
```

As you may have noticed, the `handlePostavigation()` function will be responsible for generating a random integer that will be used as a route parameter. This parameter will be defined later in the router setup.

### Create the Api Client

In today's example, we are going to consume two API endpoints [JSONPlaceholder](https://jsonplaceholder.typicode.com/) to get a specific post and its comments.

```js
// @src/api.js
export const getPostById = async (postId) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`
  );
  return await response.json();
};

export const getCommentsByPostId = async (postId) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}/comments`
  );
  return await response.json();
};
```

### Build the Pages

With the Layout of our application created, we can start working on the pages. The first will be `Home.jsx`:

```js
// @src/pages/Home.jsx
const Home = () => {
  return (
    <div>
      <h1>Home page</h1>
    </div>
  );
};

export default Home;
```

Now let's go to the most important page of this article, the one where we will use the loader to get the article and the respective comments from the [loader](https://reactrouter.com/en/main/route/loader#loader).

But first of all let's reflect a little on what we really need, because with this approach, we can reduce the number of spinners in the app and we can enjoy the features of `<Suspense>`.

The most important content on the page would be the content of the post itself and the comments would be secondary, that is, we can fetch the post content in the loader, but then we use the [defer](https://reactrouter.com/en/main/utils/defer#defer) function so that later we can fetch the comments.

Now comes the question, how could we later fetch the comments, the answer is quite simple, we can enjoy the benefits of the `<Await>` component that renders the deferred values and even handles errors automatically.

```js
// @src/pages/RandomPost.jsx
import { Suspense } from "react";
import { defer, useLoaderData, Await } from "react-router-dom";

import { getCommentsByPostId, getPostById } from "../api";

export const postLoader = async ({ params }) => {
  const postId = params.postId;
  const post = await getPostById(postId);
  const commentsPromise = getCommentsByPostId(postId);
  return defer({ post, commentsPromise });
};

const RandomPost = () => {
  const { post, commentsPromise } = useLoaderData();

  return (
    <section>
      <h2>{post.title}</h2>
      <p>{post.body}</p>

      <Suspense fallback={<small>Loading Comments...</small>}>
        <Await resolve={commentsPromise}>
          {(comments) =>
            comments.map((comment) => (
              <span key={comment.id}>
                <small>{comment.body}</small>
                <br />
              </span>
            ))
          }
        </Await>
      </Suspense>
    </section>
  );
};

export default RandomPost;
```

In the `loader()` arguments we have access to some properties and one of them is the `params` object that in this case we need the `postId`, as well as what is returned from `loader()` is easily consumed by the `useLoaderData()` hook.

### Router Setup

With the components and pages created, all that remains is to register our routes and assign the *RandomPost* page loader to the route.

```js
// @src/App.jsx
import {
  Route,
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
} from "react-router-dom";

import Layout from "./components/Layout";
import HomePage from "./pages/Home";
import RandomPostPage, { postLoader } from "./pages/RandomPost";

const router = createBrowserRouter(
  createRoutesFromElements(
    <Route element={<Layout />}>
      <Route index element={<HomePage />} />
      <Route
        path="/post/:postId"
        loader={postLoader}
        element={<RandomPostPage />}
      />
    </Route>
  )
);

export const App = () => {
  return <RouterProvider router={router} />;
};
```

## Conclusion

As usual, I hope you enjoyed the article and that it helped you with an existing project or simply wanted to try it out.

If you found a mistake in the article, please let me know in the comments so I can correct it. Before finishing, if you want to access the source code of this article, I leave [here](https://github.com/FranciscoMendes10866/router-loader-defer) the link to the github repository.