KorhanE
KorhanE

Reputation: 21

react-router-dom loader use : If the fetch of the data is slow, how can we display a LoadingIndicator component

I'm using [email protected] and [email protected]

I have a simple Message Posting demo application to try react-router-dom features, and I couldn't figure how to handle a certain case:

This is my main.jsx:

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost, { action as newPostAction } from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        path: "/",
        element: <Posts />,
        loader: postsLoader,
        children: [
          { path: "create-post", element: <NewPost />, action: newPostAction },
        ],
      },
    ],
  },
]);

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

ReactDOM.createRoot(rootElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

As you see, I'm using a loader function to load the posts to be used with Posts component. Here is Posts.jsx for reference:

import PostsList from "../components/PostsList";
import { Outlet } from "react-router-dom";

function Posts() {
  return (
    <>
      <Outlet />
      <main>
        <PostsList />
      </main>
    </>
  );
}

export default Posts;

export async function loader() {
  const response = await fetch("http://localhost:8080/posts");
  const data = await response.json();
  return data.posts;
}

And the simple RootLayout.jsx:

import { Outlet } from "react-router-dom";
import MainHeader from "../components/MainHeader";

function RootLayout() {
  return (
    <>
      <MainHeader />
      <Outlet />
    </>
  );
}

export default RootLayout;

And finally, my PostsList.jsx where I use useLoaderData to use that data in rendering posts.

import css from "./PostsList.module.css";
import Post from "./Post";
import { useLoaderData } from "react-router-dom";

function PostsList() {
  const posts = useLoaderData();

  return posts.length === 0 ? (
    <div className={css.noPosts}>
      <p>No posts found.</p>
    </div>
  ) : (
    <ul className={css.posts}>
      {posts.map((post) => (
        <Post
          key={post.id}
          enteredAuthor={post.author}
          enteredBody={post.body}
        />
      ))}
    </ul>
  );
}

export default PostsList;

The thing is, if I have a slow responding server, I'll have nothing to be displayed on screen. Even though the loader is attached to the Posts, it is the "/" route, thus RootLayout is not rendered as well, so I end up with an empty page (I tried this with adding some delay to the backend to return the response). How can I display a LoadingIndicator component when the data is still not available? Sorry if I couldn't explain my situation well enough. I am a new React learner and I would like to learn to use these new relatively new features well. Many thanks in advance.

Upvotes: 1

Views: 41

Answers (1)

Drew Reese
Drew Reese

Reputation: 203323

You could use the HydrateFallback or hydrateFallbackElement prop on the Route, but note this only works for the initial route page load.

{
  path: "/",
  element: <Posts />,
  loader: postsLoader,
  hydrateFallbackElement: <LoadingIndicator />,
  children: [
    { path: "create-post", element: <NewPost />, action: newPostAction },
  ],
},

An alternative would be to forego the route loader and implement the more conventional method of fetching data via a useEffect hook or using Redux-Toolkit Query or React-Query.

Upvotes: 0

Related Questions