react-router-dom に用意されている useRouteMatch というhooksを使って2種類のURLをマッチングして、パスパラメータを取得し数値に変換してからオブジェクトを生成してStateに保存するカスタムフックを作成しました。
このカスタムフックは特定の画面で使用するパラメータ取得用の関数で場所(URL)が変更されたことをuseEffect
で検知して、URLが2種類の内どちらかにマッチしているかを検査してからパラメータを取得するようにしています。
そのuseEffect
の引数にuseRouteMatch
の戻り値を使用して場所の変更を検知しようとしていたが、当初無限ループに陥ってしまい原因を探っていたところuseRouteMatch
が返すオブジェクトが場所が変更されなくてもuseRouteMatch
を呼ぶたびに常に新しいオブジェクトを返していることが分かりました。
これは実際に react-router の github issue でも取り上げられています。
いくつかのワークアラウンドも紹介されており、react-router v5を使っている場合はその対処を行うことで場所の変更をuseEffectで検知して処理を行うことが可能です。
そして、react-router 6.1.0にてuseRouteMatch
に変わるuseMatch
というhooksが返すオブジェクトをメモ化する対応がとられているので、それ以降のバージョンでは場所が変更されない限りは同一オブジェクトを返すようになっています。
まずは初期の実装例を掲載します。
※掲載コードは雑に書いてしまったので、細かいところおかしかったらごめんなさい。
export const useHogeParams = () => { const aMatched = useRouteMatch<HogeAPrams>('hoge/a/:aId/b/:bId/c/:cId'); const bMatched = useRouteMatch<HogeBPrams>('hoge/c/:cId'); const [hogeId, setHogeId] = useState<HogeId | null>(null); useEffect(() => { if (aMatched) { setHogeId({ typ: 'AUrlPattern', cId: parseInt(aMatched.params.cId, 10), }); return; } if (bMatched) { setHogeId({ typ: 'BUrlPattern', aId: parseInt(bMatched.params.aId, 10), bId: parseInt(bMatched.params.bId, 10), cId: parseInt(bMatched.params.cId, 10), }); return; } throw new Error(`invalid url path pattern: ${location.pathname}`); }, [aMatched, bMatched]); return hogeId; };
このコードではuseRouteMatch
の引数にパスの形式を渡して、その形式にマッチしていたらMatch
オブジェクトを返します。
マッチしていなかったらnull
を返します。
useEffect
の依存配列にはaMatched
、bMatched
を設定していて、aMatched
、bMatched
の変更があった場合はuseEffect
内の処理を実行するようにしてました。
想定では場所が変わらなければaMatched
、bMatched
のインスタンスは同一のものだと思っていたのですが、そうではなかったため、無限に呼ばれ続けていました。
これを回避するためにgithub issueに載っていた方法を参考に以下のように修正しました。
export const useHogeParams = () => { const location = useLocation(); const [hogeId, setHogeId] = useState<HogeId | null>(null); useEffect(() => { const aMatched = location.pathname.match('hoge/a/:aId/b/:bId/c/:cId'); const bMatched = location.pathname.match('hoge/c/:cId'); if (aMatched) { const cId = aMatched?.[1]; if (!cId) return; setHogeId({ typ: 'AUrlPattern', cId: parseInt(cId, 10), }); return; } if (bMatched) { const aId = bMatched?.[1]; const bId = bMatched?.[2]; const cId = bMatched?.[3]; if (!(aId && bId && cId)) return; setHogeId({ typ: 'BUrlPattern', aId: parseInt(aId, 10), bId: parseInt(bId, 10), cId: parseInt(cId, 10), }); return; } throw new Error(`invalid url path pattern: ${location.pathname}`); }, [location.pathname]); return hogeId; };
useLocation
のオブジェクトを監視する方法です。
このlocation.pathname
が変更されたときだけuseEffect
内の処理が呼ばれるように修正します。
そこから指定の形式のパターンにマッチしているかどうかを判定してStateにセットするという実装にしました。
なのでuseRouteMatch
が使えないというか使うケースが限られてしまっていますが、react-router 6.1.0 以降からはuseMatch
を使うことで同じように書くことが可能となります。
このようなケースがあるかどうかわかりませんが、同じようなことをやりたい人向けに実装の参考になればと思います。