how to use clientLoader in Remix SPA mode
As of now, we can’t use the clientLoader with HydrateFallback except for the root file. Once HydrateFallback is placed in the root, it will be used as a fallback in every page. For example, we can write the following code at the top level page components.
route.tsx
export function HydrateFallback() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<div>Loading.........</div>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
TopPage.tsx
export async function clientLoader({}: ClientLoaderFunctionArgs): Promise<
number[]
> {
return new Promise((resolve) => {
return setTimeout(() => {
return resolve([1, 2, 3]);
}, 1000);
});
}
export default function TopPage() {
const data = useLoaderData<typeof clientLoader>();
return (
<div>
{data.map((id) => (
<div key={id}>
<Link to={`/about/page${id}`}>Page {id}</Link>
</div>
))}
<Outlet />
</div>
);
}
However, we typically need to have fallbacks in nested pages. Otherwise, parent components will wait for all the data fetching happening in the children components. To avoid this, we can make clientLoader return a promise in a object.
export function clientLoader() {
const promise = new Promise<{ id: number }>((resolve) => {
return setTimeout(() => {
return resolve({
id: Math.random(),
});
}, 1500);
});
return defer({ promise });
}
export default function NestedPage() {
const data = useLoaderData<typeof clientLoader>();
return (
<div>
<Await resolve={data.promise}>
{(resolved) => <span>{resolved.id}</span>}
</Await>
</div>
);
}
It can work without defer
but it’s required to get the correct type inference.