React Routerでパンくずリスト

Remix時代の情報でだいたい実装できるけど、細かい工夫が必要になったので、もう少し踏み込んだ記事を書きたい。

handle

各ルートモジュールの handle を使い、ルートが持つメタデータをexportする。あとでパンくずリストで使う。

export const handle: Handle = {
  breadcrumb: {
    title: "一覧画面",
    path: () => "/users",
  },
};
export const handle: Handle = {
  breadcrumb: {
    title: "詳細画面",
    path: (params) => `/users/${params.id}`,
  },
};

loader のように handle に型が生成されるわけではないので、定義しておくといい。

export interface Handle {
  breadcrumb: {
    title: string;
    path: (params?: Params) => string | null;
  };
}

Params 型はReact Routerが公開している型で任意の string 型のプロパティに対して string 型の値を返す。

useMatches

パンくずリストのコンポーネント内では useMatches を使い、URLにマッチしているルートモジュール handle を参照する。この際、 handleunknown 型なので、型ガード関数を使う。

function isHandle(handle: unknown): handle is Handle {
  return (
    typeof handle === "object" && handle !== null && "breadcrumb" in handle
  );
}

useMatches を使って各ルートモジュールの handle からパンくずリストで使うデータを収集する。 match.paramsParams 型になっているため、これでパスパラメータを含むパスを生成できる。

const items = useMatches()
  .map((match) =>
    isHandle(match.handle)
      ? {
          title: match.handle.breadcrumb.title,
          path: match.handle.breadcrumb.path(match.params),
        }
      : null,
  )
  .filter((match) => match !== null);

要件次第ではあるけど、現在のURLにマッチするリンクはリンクにしないなどの処理が必要になると思うので、 useLocation でURLを参照することもあるだろう。

感想

各ルートモジュールから生成される型情報をもう少し活用できたらよかったけど、いったんこれでよしとした。 handle で定義するよりもこういったメタデータも生成してほしい気がする。