さまざまな表示を一つのパスでルーティングするReact Routerの活用

## はじめに
React Routerは簡単にクライアントサイドルーティングを実現できるライブラリです。

業務で使用しているときに、同じパスでも条件によって表示させるコンポーネントを変えたい、という処理が発生したので、どのように対応したのかをまとめていきます。

### 環境
以下のものを使っています。
| ライブラリ | バージョン | 
|:-|-:|
| React | 18.2.0 |
| TypeScript | 5.2.2 |
| Vite | 5.2.0 |
| ReactRouter | 6.23.1 |

## 古い分岐処理の書き方
現場で対応したときは`Route`コンポーネントの`render`に対して、条件分岐を書いてコンポーネントを設定していましたが、あまりそれはよろしくない書き方だったようです。

```react:よくない書き方
<Route
  path="/dashboard"
  render={() => {
    const userRole = checkUserRole();
    // ここで条件分岐を書いている
    return <>{userRole === "admin" ? <AdminView /> : <UserView />}</>;
  }}
/>;
```

`render`の中には何でもかけるんですが、処理が複雑になる可能性も考えると、コンポーネントとして切り出したほうがよさそうです。

## 新しい分岐処理の書き方
新しいバージョン（ReactRouterのv6から）では`Route`コンポーネントは使わず、`RouteObject`と`createBrowserRouter`でルーティングの情報を設定します。

https://reactrouter.com/en/main/routers/create-browser-router

`Route`コンポーネントを使わないと`render｀が使えないのでどのように分岐させるか、というところですが、そのような分岐はラッパーのコンポーネントを作成して対応します。

### メインの処理
今回はクエリパラメータの有無によって表示するコンポーネントを分岐させるようにします。
まず、メインの処理は以下のようになります。
```react:main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import {
  RouteObject,
  RouterProvider,
  createBrowserRouter,
  useSearchParams,
} from "react-router-dom";
import App from "./App";
import Child from "./Child";
import OtherChild from "./OtherChild";

// ラッパーコンポーネント
const WrapperChild = () => {
  const [searchPrams] = useSearchParams();
  const param = searchPrams.get("param");
  return param ? <Child /> : <OtherChild />;
};

// ルーティングの定義
const routes: RouteObject[] = [
  { path: "/", element: <App /> },
  { path: "/child", element: <WrapperChild /> },
];

// ルーターの生成
const router = createBrowserRouter(routes);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

```

#### ルーティングの定義
`/child`のパスに対してクエリパラメータがくっついてくる想定です。
`element`プロパティに対して、分岐処理を記載したコンポーネントを設定します。
これにより、ルーティングの定義自体がごちゃごちゃしなくなります。


#### ラッパーコンポーネント
分岐処理を書いたコンポーネント内では`useSearchPrams`を使ってクエリパラメータを取得しています。
これもReactRouterで用意しているメソッドになります。

取得した`searchParams`から取得したいパラメータを`get`メソッドで取得します。
取得できなかった場合にはエラーにならず、`null`が設定されます。

そして`pram`の値によって返すコンポーネントを分岐させています。

以上がメインの主な処理になります。

### その他コンポーネント
直接分岐処理に関わるわけではないので、詳細は以下から確認してください。

<details><summary>その他コンポーネントを確認する</summary>

```react:App.tsx
import { Link } from "react-router-dom";

const App = () => {
  return (
    <>
      <ul>
        <li>
          <Link to="/child">パラメータなし</Link>
        </li>
        <li>
          <Link to="/child?param=admin">アドミン</Link>
        </li>
        <li>
          <Link to="/child?param=member">メンバー</Link>
        </li>
      </ul>
    </>
  );
};

export default App;
```

```react:Child.tsx
const Child = () => {
  return <h1>Child Component</h1>;
};

export default Child;
const OtherChild = () => {
  return <h1>OtherChild Component</h1>;
};

export default OtherChild;
```

```react:OtherChild.tsx
```

</details>

### 挙動の確認

Topページは以下のようになっています。
ReactRouterの`Link`コンポーネントを使ってパラメータの設定したリンクを作りました。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/735ac1bb-aea5-551a-f287-8341a1e4103a.png'>

アドミンをクリックすると
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/16df13c6-5a86-91b1-7217-ce83d569cee8.png'>
パラメータが存在するので、`Child`コンポーネントが表示されます。

メンバーのときも同様です。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/78700bfb-c1fb-433d-c95a-86d9111b8762.png'>

ですが、パラメータなしの場合は挙動が変わります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/7f91bf5a-bf3a-b356-c510-f7331eca38ff.png'>
クエリパラメータが取得できなかったため、`OtherChild`コンポーネントが表示されました。

このように、パラメータの有無で同じ`child`パスへのアクセスであっても表示するコンポーネントを分岐させることができました。

## まとめ
そもそもv6のオブジェクト形式でルーティングを定義する方法は個人的に見やすくて好きだなと思いました。

また、`render`にごちゃごちゃ書くよりも、コンポーネントをしっかり分けたほうが見やすくていです。
ただ、この書き方は`render`にかくとしても使えそうなので覚えておきたいです。

また、同じパスなのに表示させるコンポーネントが違うのはユーザーにとって混乱の原因になったり、逆に処理の複雑化になったりすることもあるので、そもそもルーティングの見直しなども必要なのかなと思いました。