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は使えない

    ログを見ると、useLoaderDataerrorElementで使おうとしていることを指摘しているログが残っていました。

    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 in Layout.

    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なら動くのか、などまでは追えていませんが、とりあえずこの二つを使い分けることがあるのがわかったのは収穫です。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark