はじめに
React Routerは簡単にクライアントサイドルーティングを実現できるライブラリです。
業務で使用しているときに、同じパスでも条件によって表示させるコンポーネントを変えたい、という処理が発生したので、どのように対応したのかをまとめていきます。
環境
以下のものを使っています。
ライブラリ | バージョン |
---|---|
React | 18.2.0 |
TypeScript | 5.2.2 |
Vite | 5.2.0 |
ReactRouter | 6.23.1 |
古い分岐処理の書き方
現場で対応したときはRoute
コンポーネントのrender
に対して、条件分岐を書いてコンポーネントを設定していましたが、あまりそれはよろしくない書き方だったようです。
<Route
path="/dashboard"
render={() => {
const userRole = checkUserRole();
// ここで条件分岐を書いている
return <>{userRole === "admin" ? <AdminView /> : <UserView />}</>;
}}
/>;
render
の中には何でもかけるんですが、処理が複雑になる可能性も考えると、コンポーネントとして切り出したほうがよさそうです。
新しい分岐処理の書き方
新しいバージョン(ReactRouterのv6から)ではRoute
コンポーネントは使わず、RouteObject
とcreateBrowserRouter
でルーティングの情報を設定します。
Route
コンポーネントを使わないと`render`が使えないのでどのように分岐させるか、というところですが、そのような分岐はラッパーのコンポーネントを作成して対応します。
メインの処理
今回はクエリパラメータの有無によって表示するコンポーネントを分岐させるようにします。
まず、メインの処理は以下のようになります。
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
の値によって返すコンポーネントを分岐させています。
以上がメインの主な処理になります。
その他コンポーネント
直接分岐処理に関わるわけではないので、詳細は以下から確認してください。
その他コンポーネントを確認する
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;
const Child = () => {
return <h1>Child Component</h1>;
};
export default Child;
const OtherChild = () => {
return <h1>OtherChild Component</h1>;
};
export default OtherChild;
挙動の確認
Topページは以下のようになっています。
ReactRouterのLink
コンポーネントを使ってパラメータの設定したリンクを作りました。
アドミンをクリックすると
パラメータが存在するので、Child
コンポーネントが表示されます。
ですが、パラメータなしの場合は挙動が変わります。
クエリパラメータが取得できなかったため、OtherChild
コンポーネントが表示されました。
このように、パラメータの有無で同じchild
パスへのアクセスであっても表示するコンポーネントを分岐させることができました。
まとめ
そもそもv6のオブジェクト形式でルーティングを定義する方法は個人的に見やすくて好きだなと思いました。
また、render
にごちゃごちゃ書くよりも、コンポーネントをしっかり分けたほうが見やすくていです。
ただ、この書き方はrender
にかくとしても使えそうなので覚えておきたいです。
また、同じパスなのに表示させるコンポーネントが違うのはユーザーにとって混乱の原因になったり、逆に処理の複雑化になったりすることもあるので、そもそもルーティングの見直しなども必要なのかなと思いました。