エラーと読み込み中の表示を出す
ページが描画中に例外を投げたときの表示と、ページの準備ができるまでの表示は、ルート表のオブジェクトに書きます。このページでは、errorとloadingの書き方と、React.lazyで分けたページの待ち方を説明します。
このページの内容
エラーを表示する
childrenを持つオブジェクトにerrorを書くと、その下のページが描画中に例外を投げたとき、ページの代わりにerrorのコンポーネントが描かれます。描かれる場所はレイアウトの<Outlet />の位置なので、レイアウトはそのまま残ります。
src/routes.tsexport const routes = defineRoutes({
'/products': {
layout: ProductsLayout,
error: ProductsError,
children: {
'/': ProductList,
'/:id': ProductPage,
},
},
});src/products-error.tsximport type { ErrorProps } from '@k8ordo/router';
export function ProductsError({ error, reset }: ErrorProps) {
const message =
error instanceof Error ? error.message : 'Something went wrong';
return (
<div role="alert">
<p>{message}</p>
<button onClick={reset} type="button">
Try again
</button>
</div>
);
}errorのコンポーネントは、投げられた値をerrorで、描き直すための関数をresetで受け取ります。errorは何が投げられてもよいようにunknown型です。
reset()を呼ぶと、その場で下のページをもう一度描きます。また例外を投げれば、errorのコンポーネントに戻ります。
エラーの表示は、別のページに移ると消えます。消えるのは次のページが画面に出たときで、オブジェクトの下にあるレイアウトは作り直されず、状態を保ったままです。
ただし、クエリ文字列だけを変える状態の更新はページの切り替えではないので、エラーの表示は残ります。
メモ
レイアウト自身が投げた例外は、同じオブジェクトのerrorでは受け止められません。errorのコンポーネントはレイアウトの内側に描かれるからです。その例外は外側のオブジェクトのerrorに届き、どこにも無ければ<Router>の外まで伝わります。
読み込み中の表示を出す
loadingには、その下のページがサスペンドしている間に出すコンポーネントを書きます。このコンポーネントはpropsを受け取りません。
src/routes.tsexport const routes = defineRoutes({
'/products': {
layout: ProductsLayout,
error: ProductsError,
loading: ProductsLoading,
children: {
'/': ProductList,
'/:id': ProductPage,
},
},
});src/products-loading.tsxexport function ProductsLoading() {
return <p role="status">Loading products…</p>;
}loadingを書いた位置には<Suspense>が置かれ、loadingのコンポーネントがそのfallbackになります。errorも書いたときは、この<Suspense>はエラーを受け止める位置より内側に入ります。
fallbackが出るのは、ページを最初に開いたときと、ほかのページからこのオブジェクトの下へ移ってきたときです。
すでにこのオブジェクトの下にいて、別のページへ移るときはfallbackを出しません。次のページの準備ができるまで、前のページを出したままにします。その間の待ちを見せたいときは、「いまいる場所を調べる」で説明するusePendingPathnameを使います。
ページのコードを分けて読み込む
ページのコンポーネントは、React.lazyで包んでからルート表に書いてもかまいません。そのページを初めて描くときに、コードが読み込まれます。
src/routes.tsimport { defineRoutes } from '@k8ordo/router';
import { lazy } from 'react';
const Settings = lazy(() => import('./pages/settings'));
export const routes = defineRoutes({
'/': {
layout: Shell,
loading: PageLoading,
children: {
'/': Home,
'/settings': Settings,
},
},
});コードが届くまでの間は、上にある<Suspense>のfallbackが出ます。loadingを書くか、レイアウトの中で<Outlet />を<Suspense>で包んで、fallbackを出す場所を用意してください。
fallbackが出る場面はloadingと同じです。上の例では/のオブジェクトがすべてのページを包んでいるので、/settingsを直接開いたときはfallbackが出ます。一方で、ホームから/settingsへ移ったときは、コードが届くまでホームが出たままです。
落とし穴
errorを書いたオブジェクトは、下のページをfallbackがnullの<Suspense>でも包みます。サーバーでの描画で例外を投げた部分を、ブラウザに任せるためです。そのため、その下でReact.lazyのページがサスペンドしても、上のレイアウトの<Suspense>までは届かず、何も表示されません。fallbackを出したいときは、同じオブジェクトにloadingを書くか、それより下に<Suspense>を置いてください。