framer-motion使おうとしたらなぜかuseLocationとRouterで躓いた話

#経緯
最近Reactを使う機会が増えました。
Reactを夢中で触っているうちに、ふと自分のやってみたかったことの1つを思い出しまして。
###別ページを表示するときのあのアニメーションを取り入れてみたい…！
（調べたところページトランジションというみたいですね）
今回は簡単にではありますがそれを実践してみようかと。
#使用したライブラリ
* react (v17.0.2)
* react-router-dom (v5.3.0)
* __framer-motion (v4.1.17)__

今回は環境構築等は省きます。
また、複数のページのルーティングを行っている前提で話を進めます。
#アニメーションの実装方法
アニメーション自体は以下のようなコードでも実装できます。

```typescript:Home.tsx
import React from "react";
import {motion} from "framer-motion";

const Home = () => {
    return <motion.div initial={{x:"-100%"}} animate={{x:["-100%","0%"]}} >
        Home
    </motion.div>
}
export default Home;
```

これは、ページを読み込んだ際にHomeという文字を左画面外から元々の位置へ移動させるアニメーションが実行されるコードです。
ただし、この状態ではこのコンポーネントがマウントされる際のアニメーションはオッケーなのですが、アンマウントされる際のアニメーションが実装できていません。
#exit処理が使えるらしい。
調べていくうちに、以下の資料からexitの存在を知りました。

https://www.framer.com/docs/animate-presence/

この資料によると…

* animateはマウント時のアニメーションを定義している
* exitはアンマウント時のアニメーションを定義している
* アニメーションしたいコンポーネントの親要素として、AnimatePresenceというコンポーネントが必要
* AnimatePresenceの子コンポーネントにはkeyを定義しておく必要がある

…とのこと。

#それっぽく書いてみた
とりあえず書いてみないことには始まらないわけで。それっぽくコードを変更。

```typescript:Home.tsx
import React from "react";
import {motion,AnimatePresence} from "framer-motion";

const Home = () => {
    return <AnimatePresence>
        <motion.div initial={{x:"-100%"}} animate={{x:["-100%","0%"] exit={{x:["0%","100%"]}}>
            Home
        </motion.div>
    <AnimatePresence>
}
export default Home;
```

こんな感じ。先ほどのコードに加え、必要なコンポーネントとアンマウント時のアニメーションを定義しました。
…お気づきかもしれませんが、このコード、keyの定義を忘れています。当然エラーが発生し、ここから迷走することに。

#keyの定義
これまた色々調べているうちに、子コンポーネントにkeyの定義が必要なことに気づきました。
同時に、keyにはuseLocationなどで取得できるlocationがよく用いられている（？）ことにも気づきます。

https://stackoverflow.com/questions/67974970/animate-presence-exit-not-working-framer-motion

https://js-challenge.dev/posts/page-transition-with-react-router-and-framer-motion/

まだまだ経験不足ということもあり、keyというものが何かよくわかっていませんでしたが、とりあえず書いてある通り実践してみることに。

#locationをkeyに設定した
やり方が分かったので、凡例に倣ってコードを変えてみました。
今回は大元のApp.jsを変更。

```typescript:App.tsx
import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
  useLocation,
} from "react-router-dom";
import {AnimatePresence} from "framer-motion";


import Navigation from "./components/Navigation";
import Home from "./components/Home/Home";
import About from "./components/About/About";
import Contact from "./components/Contact/Contact";

import "./App.css";

function App() {
  const location = useLocation();
  return (
    <Router>
      <div className="header">Header</div>
      <Navigation></Navigation>
      <div>
          <AnimatePresence exitBeforeEnter>
              <Switch location={location} key={location.pathname}>
                  <Route exact path="/">
                      <Home></Home>
                  </Route>
                  <Route exact path="/about">
                      <About></About>
                   </Route>
                   <Route exact path="/contact">
                      <Contact></Contact>
                   </Route>
              </Switch>
          </AnimatePresence>
      </div>
    </Router>
  );
}

export default App;
```

ポイントとしては、

* key用のlocation取得にuseLocationを使用
* Switchの親コンポーネントにAnimatePresenceを使用

どうやら各コンポーネントにいちいちAnimatePresenceコンポーネントを記述する必要はなく、Switchの親コンポーネントとしてAnimatePresenceを記述すれば問題ないようです。
この時点で、先ほどHome.tsxで記述したAnimatePresenceコンポーネントは消しています。

それでは実行！！！！wktk
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1692273/9a02ad12-713e-39b7-8b2d-8441e98d6abd.png)

…

なんぞこれ…

#よく見るCannot read properties of undefinedだけど…
エラー自体はよく見るやつ。ただ…なぜこのタイミング？
エラーが発生してる場所のコードも問題なさげ。誤字もしてない。
原因が分からんんんんんんん～～～～～～～～～～！！！！
原因を探るべく、またしてもネットの海へダイブ。
あれでもない、これでもない…
２時間くらいこれのためだけにネットサーフィンする羽目になりました。勉強が足りん。
そして…
#原因判明

https://github.com/remix-run/react-router/issues/7015

このエラー、どうやらRouterとuseLocation(hooks)の位置が悪かったために起きたエラーのようです。

###エラーの起きる条件
* Router(BrowserRouter)とhooksが同じコンポーネントファイルにあること
* hooksがRouter(BrowseRouter)の子コンポーネント以下に設置してあること

そんなんしらん…(´・ω・｀)
というわけで早速修正。


```typescript:App.tsx
import React from 'react';
import {
  Switch,
  Route,
  useLocation,
} from "react-router-dom";
import {AnimatePresence} from "framer-motion";


import Navigation from "./components/Navigation";
import Home from "./components/Home/Home";
import About from "./components/About/About";
import Contact from "./components/Contact/Contact";

import "./App.css";

function App() {
  const location = useLocation();
  return (
    <div>
      <div className="header">Header</div>
      <Navigation></Navigation>
      <div>
          <AnimatePresence exitBeforeEnter>
              <Switch location={location} key={location.pathname}>
                  <Route exact path="/">
                      <Home></Home>
                  </Route>
                  <Route exact path="/about">
                      <About></About>
                   </Route>
                   <Route exact path="/contact">
                      <Contact></Contact>
                   </Route>
              </Switch>
          </AnimatePresence>
      </div>
    </div>
  );
}

export default App;
```
App.tsxからRouterを削除し…

```typescript:index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {
  BrowserRouter as Router,
} from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <Router>
    <App />
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();
```

Appを呼び出しているindex.tsxにRouterを移しました。

#無事解決！
動画を見せることはできませんが、無事にマウント時・アンマウント時の両方でアニメーションを実行することができました！これでこれからframer-motionを使って色々遊べそうです。
#まとめ
今回はframer-motionを使うときに、useLocationとRouterの使い方で躓いたお話でした。（実はframer-motion自体は間接的に関係しただけで、実際はreact-routerとhooksの問題だったけど。）
keyにlocationを設定しようとしたために躓いたエラーだったけど、今後も出てきそうなエラーだったのでそういう意味では良い経験になったかなと。
あのエラー画面苦手だからあんまり見たくないが…(´-ω-`)
以上です。同じようなエラーに直面した時にはhooks等の位置確認もしてみたら幸せになれるかも？
ではでは。('ω')ノシ

#参考文献
今回参考にさせて頂いた記事をもう一度まとめておきます。

https://www.framer.com/docs/animate-presence/

https://stackoverflow.com/questions/67974970/animate-presence-exit-not-working-framer-motion

https://js-challenge.dev/posts/page-transition-with-react-router-and-framer-motion/

https://github.com/remix-run/react-router/issues/7015