O hirunewani blog

Q. React Routerでページ遷移してもErrorBoundaryがリセットされない

Created at

React Routerを利用している環境で、一度エラーが表示された後に、パスパラメーターが異なるURLに遷移しても、ErrorBoundaryによって表示されたエラーが画面から消えないと相談を受けた。その理由と対応策について書いた。

React Routerを利用している環境で、一度エラーが表示された後に、パスパラメーターが異なるURLに遷移しても、 ErrorBoundaryによって表示されたエラーが画面から消えないと相談を受けた。

状況

書かれていたコードはかなり簡略化して書くと次のようになっていた。 ItemListのリンクを踏むとパスパラメーターが変わり、ItemDetailsに表示される内容が変わるというページである。つまり、一度ItemDetailsでエラーが出てしまうと、ItemListで別のアイテムを選択してもエラーが消えないという状況であった。

function Page() {
  const { id } = useParams();
  return (
    <div>
      <ItemList />
      <ErrorBoundary fallback={<ErrorFallback />}>
        <ItemDetails id={id} />
      </ErrorBoundary>
    </div>
  );
}
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <Page />,
      },
      {
        path: ":id",
        element: <Page />,
      },
    ],
  },
]);

原因

まず、ErrorBoundaryはReact Routerのナビゲーションによってリセットされないことは正常に思える。 なぜなら、ErrorBoundaryのfallbackが表示された時点で、idは関係なく、ナビゲーションしても同一のコンポーネントが再利用されるためである。

次のような状態と同じである。

function Page() {
  const { id } = useParams();
  return (
    <div>
      <ItemList />
      <ErrorFallback />
    </div>
  );
}

対応策その1 key

keyを利用してErrorBoundaryをリセットすればいい。

function Page() {
  const { id } = useParams();
  return (
    <div>
      <ItemList />
      <ErrorBoundary fallback={<ErrorFallback />} key={id}>
        <ItemDetails id={id} />
      </ErrorBoundary>
    </div>
  );
}

keyの利用に抵抗感がある人もいるかもしれないが、公式ドキュメントでもナビゲーション時にサスペンスバウンダリをリセットする方法としてkeyが紹介されている。ただし気軽に多用していいわけではない。

https://ja.react.dev/reference/react/Suspense#resetting-suspense-boundaries-on-navigation

対応策その2 errorElement

もう1つの方法として、React RoutererrorElementを利用する方法がある。 これを利用すると、あまり明確に言及されていない気がするが、ナビゲーション時にErrorBoundaryがリセットされる。

https://reactrouter.com/en/main/route/error-element

ただし、今回のケースでは書き換えが必要だと思われる。 errorElementを対応箇所にのみ適用させるために、リンク部分をItemListからLayoutに移動するように変更する必要がある。

function Page() {
  const { id } = useParams();
  return <ItemDetails id={id} />;
}
const router = createBrowserRouter([
  {
    path: "/",
    element: <LayoutWithItemList />,
    children: [
      {
        index: true,
        element: <Page />,
      },
      {
        path: ":id",
        errorElement: <ItemDetailsErrorBounday />,
        element: <Page />,
      },
    ],
  },
]);

個人的にはerrorElementを利用すると適切なルーティングの設計を促されているように感じるため、こちらを推奨したい。