React Router v6.4 で v5 から脱出したい

React Router v6 がリリースされてから凡そ半年が立ちました。

そして、v6.4 の足音が近づいてきています。 まだ v5 を利用している方は、これを機にバージョンを上げてみませんか?

上げたくても上げられない

もちろん訳あって上げられない場合もあるかと思います。

私が参加しているプロジェクトでも、流行に遅れてつい先日まで v5 でした。 なぜなら、v6 には preload API がなかったのです。*1

これは、Render-as-you-fetch パターンを導入しているアプリケーションにとって v6 バージョンアップへのハードルを上げていました。

v5 では、以下のように Route コンポーネントが持つ render prop*2 からデータ取得(Relay の loadQuery など)を挟んだ関数を渡すことができました。

<Route path="/home" render={() => {
  const queryRef = loadQuery(environment, homeQuery, ...)
  return <Home queryReference={queryRef} ... />
  }} />

そして v6.4 では、これを解決した loader prop がやってきます。 それを提供してくれるのが、DataBrowserRouter です。

現時点(2022/06/19)ではまだ beta ですが、公式ドキュメントが用意されているので見てみてください。 Data Quick Start | React Router

以下のように、loader prop に渡した関数が返すデータを useLoaderData フックを使って取得することができます。

// routing
  ...
  <Route element={App} loader={getAppData} />

// App.tsx
  const data = useLoaderData()
  return <div>{data.name}</div>

はい簡単ですね。ではサクッと v5 から脱出してしまいましょう。

React Router v6.4 と遷移アニメーション

しかし、然うは問屋が卸さないのがメジャーバージョンアップの作業です。

例として、v5 の Switch コンポーネントと framer-motion を組み合わせて、ルーティング遷移にアニメーションを実装している場合を考えてみます。 (もちろん render func でデータ取得も行っているとします)

<AnimatePresence>
  <Switch location={location} key={...} >
    <Route path="/" ... />
    <Route path="/:id" ... />
  </Switch>
</AnimatePresence>

さて、これを v6 用に直すと以下のように書けます。

<AnimatePresence>
  <Routes location={location} key={...} >
    <Route path="" ... />
    <Route path=":id" ... />
  </Routes>
</AnimatePresence>

Switch を Routes に置き換えるだけです。はい簡単ですね。

しかし、DataBrowserRouter は Route コンポーネントしか受け付けていません。そうです、Routesコンポーネントが使えないのです。 Switch を使ってアニメーションを行っていた場合は Route コンポーネントのみで実装する必要があるのです。

ここで、React Router v6 で追加された Outlet という機能が生きてきます。これを使うと、コンポーネントとルーティングの実装を分離させることができます。

先程 v6 用に書き直したものをさらに DataBrowserRouter とアニメーションを組み合わせた、v6.4 向けに書き直してみます。

// routing
<Route path="*" element={Wrapper} >
  <Route path="" element={...} />
  <Route path=":id" element={...} />
</Route>

// Wrapper.tsx
const Wrapper = () => {
const outlet = useOutlet()
return (
<AnimatePresence>
  <div key={location.pathname}>
    {outlet}
  </div>
</AnimatePresence>

上記のように、useOutlet が返すものを div や Suspense で囲って location.pathname を key に渡してあげると、 ルーティング遷移のタイミングでアニメーションを行うことができます。

注意

location が変化すると、ルーティング定義に応じて loader prop も変わるので useLoaderData が返す値も変化してしまいます。 そのため、遷移アニメーションでは旧ページが unmount されるまで useLoaderData が返す値をキャッシュしておく必要があります。

次のデモアプリでは useLoaderData を wrap した、カスタム hook を用意しました。*3

デモ

上記の実装を用いて簡単なアニメーション付きのアプリを作ってみたので参考にしてみてください。

実際のリポジトリこちら

さあ、あなたも React Router v5 から脱出しませんか?

参考資料