I'm trying to understand how suspense / await work with nested routes.
The scenario I have:
- I have a collection page that shows a list of items.
- I have a detail page that shows the details of a single item (this page is a nested route)
What I want to achieve:
When the collection page is loaded, the page shows a waiting indicator while the data is being fetched (I have added a random delay to simulate this).
Once the data is loaded, if I click in one of the items it will navigate to the nested detail page (this page is also wrapped in a suspense / await), so we get on the left side the list and the right side the detail page for the selected character, ... here I'm showing a loading indicator.
The expected behavior:
- Collection loading indicator is shown when the collection page is loaded.
- Detail loading indicator is shown when the detail page is loaded.
The actual behavior:
- Collection loading indicator is shown when the collection page is loaded.
- Detail loading indicator is shown when the detail page is loaded, plus list page is reloaded.
How I have defined the routes:
routes.tsx
import React from "react";
import { defer, createBrowserRouter } from "react-router-dom";
import { ListPage, DetailPage } from "./pages";
import { getCharacter, getCharacterCollection } from "./api";
export const applicationRoutes = createBrowserRouter([
{
path: "/",
loader: () => defer({ characterCollection: getCharacterCollection("") }),
element: <ListPage />,
children: [
{
path: ":id",
loader: ({ params }) =>
defer({ character: getCharacter(params.id as string) }),
element: <DetailPage />
}
]
}
]);
How I have implemented each page:
list.tsx
import React from "react";
import { Await, Link, Outlet, useLoaderData } from "react-router-dom";
import { Character } from "../api/model";
export const ListPage = () => {
const characters = useLoaderData() as { characterCollection: Character[] };
return (
<div>
<h2>Character Collection</h2>
<React.Suspense
fallback={<h4 style={{ color: "green" }}> Loading characters...</h4>}
>
<Await resolve={characters.characterCollection}>
{(characters) => (
<div style={{ display: "flex", flexDirection: "row" }}>
<div>
<ul>
{characters.map((character) => (
<li key={character.id}>
<Link to={`./${character.id}`}>{character.name}</Link>
</li>
))}
</ul>
</div>
<Outlet />
</div>
)}
</Await>
</React.Suspense>
</div>
);
};
detail.tsx
import React from "react";
import { Await, useLoaderData } from "react-router-dom";
import { Character } from "../api/model";
export const DetailPage: React.FunctionComponent = () => {
const data = useLoaderData() as { character: Promise<Character> };
return (
<div>
<h1>Detail</h1>
<React.Suspense
fallback={<h4 style={{ color: "blue" }}> Loading character...</h4>}
></React.Suspense>
<Await resolve={data.character}>
{(character) => (
<div>
<img src={character.image} alt={character.name} />
<h2>{character.name}</h2>
<p>{character.status}</p>
</div>
)}
</Await>
</div>
);
};
Codesandbox example: https://codesandbox.io/s/vigilant-agnesi-z7tvk1
It seems like the way that I'm using to navigate just forces a full reload:
<Link to={`./${character.id}`}>{character.name}</Link>
Instead of updating only loading the nested page, is this behavior by default? or am I doing something wrong?