RemixのLayoutではuseRouteLoaderDataを使おう
Remixでloaderの情報を使う際、useLoaderDataよりも他のフックを利用するケースもある。LayoutコンポーネントでClerkの認証情報を使用しようとした際に、HTTP404エラーがHTTP500エラーに変わる現象が起きた。ErrorBoundary周りを調査し、useRouteLoaderDataを使うことで問題が解決した。エラーが発生する原因はloaderの情報が正しく取得できないこと。useLoaderDataとuseRouteLoaderDataを使い分けることが重要。
目次
Remixでloader
の情報を利用するならuseLoaderData
。・・・と思っていたのですが、他のフックを使う方がよいケースもある様子です。今回はLayout
コンポーネントに使おうとした時の体験をまとめました。
やりたい
Clerkの認証情報を使って、ヘッダーやフッターのコンテンツを変更しようとしました。これらのコンポーネントが、Layout
コンポーネントに配置されているため、ProviderをLayout
に設定し、loader
のデータを使う形での実装を行いました。
export async function loader(args: LoaderFunctionArgs) {
const clerkLoader = await rootAuthLoader(
args,
() => {
return {};
},
{
publishableKey: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY,
}
);
const clerkResponse = await (clerkLoader as Response).json<object>();
const userData = await loadCurrentUser(args);
return json({
...clerkResponse,
userData,
});
}
export function Layout({ children }: { children: React.ReactNode }) {
const loaderData = useLoaderData<{ clerkState: any }>();
const { currentLocale } = useCurrentLocale();
return (
<html lang="en" className="h-full bg-gray-100">
<head>
<Meta />
<Links />
</head>
<body>
<ClerkProvider
clerkState={loaderData?.clerkState}
localization={currentLocale === 'ja' ? jaJP : enUS}
>
<div className="py-10">{children}</div>
<ScrollRestoration />
<Scripts />
</ClerkProvider>
</body>
</html>
);
}
HTTP404エラーが発生すると、HTTP500エラーになる
文字にするとよくわからない現象に見えますが、「404エラーが発生した時に表示させるページ・コンポーネントで、500エラーが発生する」という現象が発生しました。今回発生していたエラーはこちらです。
Unexpected Server Error
TypeError: Cannot read properties of undefined (reading 'clerkState')
正しいステータスコードを送信したいところですので、ErrorBoundary
まわりを重点的に調査します。
errorElement
では、useLoaderData
は使えない
ログを見ると、useLoaderData
をerrorElement
で使おうとしていることを指摘しているログが残っていました。
You cannot `useLoaderData` in an errorElement (routeId: root)
TypeError: Cannot read properties of undefined (reading 'clerkState')
検索すると、 似たような現象に遭遇した方のIssueを見つけることができました。
For now, this is expected behavior and you can use
useRouteLoaderData("root")
to access the root loader data inLayout
.
useLoaderData
の代わりにuseRouteLoaderData
を使う
コメントの通り、useRouteLoaderData
に差し替えてみましょう。引数を追加する必要がある点に注意します。
export function Layout({ children }: { children: React.ReactNode }) {
const loaderData = useRouteLoaderData<{ clerkState: any }>('root');
const { currentLocale } = useCurrentLocale();
return (
<html lang="en" className="h-full bg-gray-100">
<head>
<Meta />
<Links />
</head>
<body>
<ClerkProvider
clerkState={loaderData?.clerkState}
localization={currentLocale === 'ja' ? jaJP : enUS}
>
<div className="py-10">{children}</div>
<ScrollRestoration />
<Scripts />
</ClerkProvider>
</body>
</html>
);
}
この書き方にすることで、404ページでもエラーが発生することがなくなりました。
考察
エラー系のコンポーネントに遷移する際、loader
の情報を正しく取得できなくなっていたのが原因と思われます。なぜuseRouteLoader
なら動くのか、などまでは追えていませんが、とりあえずこの二つを使い分けることがあるのがわかったのは収穫です。