React Router Data Fetching Made Easy: A Step-by-Step Guide

franciscomendes10866

Francisco Mendes

Posted on December 27, 2022

React Router Data Fetching Made Easy: A Step-by-Step Guide

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:

yarn create vite router-defer --template react
cd router-defer
Enter fullscreen mode Exit fullscreen mode

Now we can install the necessary dependencies:

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

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.

// @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;
Enter fullscreen mode Exit fullscreen mode

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 to get a specific post and its comments.

// @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();
};
Enter fullscreen mode Exit fullscreen mode

Build the Pages

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

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

export default Home;
Enter fullscreen mode Exit fullscreen mode

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.

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 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.

// @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;
Enter fullscreen mode Exit fullscreen mode

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.

// @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} />;
};
Enter fullscreen mode Exit fullscreen mode

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 the link to the github repository.

πŸ’– πŸ’ͺ πŸ™… 🚩
franciscomendes10866
Francisco Mendes

Posted on December 27, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related