react-router-dom V5のNested Routeに関して
はじめに
初めまして!
株式会社おてつたびでフルスタックエンジニアをしているぶりぼんと申します。主にフロントエンド領域を開拓しており、ReactやTypeScriptが最も得意です。
おてつたびでは、フロントエンドのライブラリにReactを、ルーティングにはreact-router-domを使用しています。
今回は、react-router-domのバージョン5でNested Routeを行う際に、はまったポイントを解説していきます。
本題
実装したかったこと
おてつたびでは、Webプロダクト開発と一緒に、iOSアプリ開発も行っております。
iOSアプリを開発する中で、時間やコストの関係上、WebView対応せざるを得ないページが出てきます。
WebView対応するページは、独自のプレフィックス(本記事ではサンプルとして/native_app
としています)をつけることで、WebView時に表示したくないものを条件分岐で非表示にするという対応をとっております。
以上を踏まえて、通常のWebページ表示とWebView表示を行うために、react-router-domで以下のように設定をしました。
(実際に書いたコードとは大きく異なるのと、コードの多くを省いて記載しています。)
const Router = () => {
return (
<BrowserRouter>
<Switch>
<SharedRoute />
<Route path="/native_app">
<SharedRoute />
</Route>
<Route path="/other_route" component={OtherRouteComponent} />
</Switch>
</BrowserRouter>
)
}
const SharedRoute = () => {
const concatNativeApp = (path: string) => {
const isNativeApp = window.location.pathname.includes("/native_app")
return isNativeApp ? `/native_app${path}` : path
}
return (
<>
<Route path={concatNativeApp("/hoge/foo")} component={HogeComponent} />
<Route path={concatNativeApp("/hoge/bar")} component={FooComponent} />
</>
)
}
上記のコードでは、/hoge/foo
と/hoge/bar
の両ページは表示されますが、/native_app/hoge/foo
, /native_app/hoge/bar
, /other_route
の3つのページは表示されません。
react-router-domのSwitchコンポーネントのコードを見ると、なぜこれが動作しないのかがよく分かります。
まずは、どうすればこのコードが動くかを提示します。
動作するコード
結論を述べると、SharedRoute
コンポーネントを、Route
コンポーネントでラップする必要があります。
これで、定義している5つのページが表示されます。
const Router = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/hoge">
<SharedRoute />
</Route>
<Route path="/native_app">
<SharedRoute />
</Route>
<Route path="/other_route" component={OtherRouteComponent} />
</Switch>
</BrowserRouter>
)
}
const SharedRoute = () => {
const concatNativeApp = (path: string) => {
const isNativeApp = window.location.pathname.includes("/native_app")
return isNativeApp ? `/native_app${path}` : path
}
return (
<>
<Route path={concatNativeApp("/hoge/foo")} component={HogeComponent} />
<Route path={concatNativeApp("/hoge/bar")} component={FooComponent} />
</>
)
}
なぜ動作しないのか
以下のreact-router-domのコードが、なぜ最初のコードでは動作しないかの理由となります。
原因は、Switch
コンポーネントにあります。
Switch
コンポーネントのURLパスのパターンマッチングは、propsに格納されているchildrenをforEach
で行っています。
さらにその内部で、子コンポーネントのpathの取得はconst path = child.props.path || child.props.from;
で行っており、再帰的にpathの取得は行っておりません。
SharedRoute
コンポーネントでRoute
コンポーネントを使ってルーティング情報を返しているので、一見すると動作するように見えます。(現に弊社エンジニアは私を含め、このコードで動作すると思っていました。。)
しかし、SharedRouteコンポーネント自体がpath
かfrom
でURLパスの情報を持たなければ、Switch
コンポーネントによるルーティングが機能しません。
ここからは推測も含みますが、PasswordResetRoute
コンポーネントがルーティング用のコンポーネントではなく、通常のコンポーネントとして処理されます。
一方で、PasswordResetRoute
内でもルーティング情報を有しているため、通常のコンポーネントとして処理されたこのコンポーネント内のページが先にマッチングすることで、他のページが表示されないという結果になっているのではないかと思われます。
react-router-domは扱いに癖があり、公式ドキュメントやコードを読まないと正しく使えないことが多々あります。
直感に反する動作をしている一方で、コードを読むことで解決するので、ルーティングが正しく動作しない場合、落ち着いてコードを読んでみることをお勧めします。
また、react-router-domは最新のバージョンで6系がリリースされています。
軽くドキュメントを読む限りでは、直感的でシンプルに書けるようになっているようです。
フロントエンドエンジニアとしては、弊社でも早く取り入れてみたいとうずうずしています。
終わりに
株式会社おてつたびでは、一緒に働く仲間を募集しています!
本記事では、今まで行ってきたWeb開発における話をしましたが、iOSアプリの開発を行っており、初期リリースを控えております。
第一号となるiOSエンジニアを募集しておりますので、まずは興味がある方は気軽にお話をしましょう!
もちろん、Webフロントエンドエンジニア、Webバックエンドエンジニアも募集しております。
引き続き、地域とのご縁を紡げるサービスであり続けるために、多くの方に興味を持ってもらいたいです。
本記事をご覧になって少しでも興味が湧いた方は、以下のリンクをチェックしてみてください!
Discussion