LoginSignup
4
1

More than 1 year has passed since last update.

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

Posted at

経緯

最近Reactを使う機会が増えました。
Reactを夢中で触っているうちに、ふと自分のやってみたかったことの1つを思い出しまして。

別ページを表示するときのあのアニメーションを取り入れてみたい…!

(調べたところページトランジションというみたいですね)
今回は簡単にではありますがそれを実践してみようかと。

使用したライブラリ

  • react (v17.0.2)
  • react-router-dom (v5.3.0)
  • framer-motion (v4.1.17)

今回は環境構築等は省きます。
また、複数のページのルーティングを行っている前提で話を進めます。

アニメーションの実装方法

アニメーション自体は以下のようなコードでも実装できます。

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の存在を知りました。

この資料によると…

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

…とのこと。

それっぽく書いてみた

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

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がよく用いられている(?)ことにも気づきます。

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

locationをkeyに設定した

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

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

なんぞこれ…

よく見るCannot read properties of undefinedだけど…

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

原因判明

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

エラーの起きる条件

  • Router(BrowserRouter)とhooksが同じコンポーネントファイルにあること
  • hooksがRouter(BrowseRouter)の子コンポーネント以下に設置してあること

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

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を削除し…

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等の位置確認もしてみたら幸せになれるかも?
ではでは。('ω')ノシ

参考文献

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

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1