はじめに
React RouterのDataモードを使ってみようと思い、loaderで非同期の処理を書いたときに詰まったので投稿します。
環境
React ^19.2.0
React Router ^7.13.0
問題
下記のようにloaderで非同期処理を実行した時に、非同期処理が不要なコンポーネント(ヘッダーなど)も描画されませんでした。
loaderがページコンポーネントの描画前に実行されるため、このような挙動になっています。
PageRouter.tsx
export const PageRoute = () => {
const router = createBrowserRouter([
{
Component: MainLayout,
children: [
{
path: 'templates/:publicId',
Component: TemplateDetail,
loader: async ({ params }: LoaderFunctionArgs): {currentPost: Promise<DeclincePost>} => {
const currentPost = await selectPost(params.publicId ?? '');
return { currentPost };
},
}
],
},
]);
return <RouterProvider router={router} />;
};
Detail.tsx
export const TemplateDetail: FC = () => {
const { currentPost } = useLoaderData<{ currentPost: DeclincePost }>();
return (
<MainContainer>
<h2>タイトル</h2>
<PostDetail post={currentPost}></PostDetail>
</MainContainer>
);
};
解決方法
React RouterのAwaitを利用して解決しました。
loaderでは、Promiseを返すように修正しました。
また、hydrateFallbackElementプロパティを追加しないと、No HydrateFallback element provided to render during initial hydrationWarningエラーが発生します。
PageRouter.tsx
hydrateFallbackElement: <></>
loader: ({ params }: LoaderFunctionArgs): { currentPostPromise: Promise<DeclincePost> } => {
const currentPostPromise = selectPost(params.publicId ?? '');
return { currentPostPromise };
},
Detail.tsx
export const TemplateDetail: FC = () => {
const { currentPostPromise } = useLoaderData<{ currentPostPromise: Promise<DeclincePost> }>();
return (
<MainContainer>
<h2>タイトル</h2>
<Suspense fallback={<Spinner />}>
<Await resolve={currentPostPromise}>
{(currentPost) => (
<PostDetail post={currentPost}></PostDetail>
)}
</Await>
</Suspense>
</MainContainer>
);
};
参考