motion icon indicating copy to clipboard operation
motion copied to clipboard

[BUG] AnimatePresence popLayout does not overlay the component in React Router 6

Open ryan-weisenberger opened this issue 1 year ago • 0 comments

When using the AnimatePresence mode="popLayout" inside of a React Router 6 route, the "popped" component is inserted into that DOM as a sibling to the entering component, so the new component is not "popped" out of the page.

This may be related to the warning that appears in the dev console which may have a fix here :

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.
    at Routes (http://localhost:5173/node_modules/.vite/deps/chunk-WKEOGKIS.js?v=087143ff:4043:5)
    at PopChildMeasure (http://localhost:5173/node_modules/.vite/deps/framer-motion.js?v=087143ff:7058:23)
    at PopChild (http://localhost:5173/node_modules/.vite/deps/framer-motion.js?v=087143ff:7079:21)
    at PresenceChild (http://localhost:5173/node_modules/.vite/deps/framer-motion.js?v=087143ff:7114:24)
    at AnimatePresence (http://localhost:5173/node_modules/.vite/deps/framer-motion.js?v=087143ff:7180:26)
    at MyRoutes (http://localhost:5173/src/MyRoutes.tsx?t=1706572828327:17:22)
    at Router (http://localhost:5173/node_modules/.vite/deps/chunk-WKEOGKIS.js?v=087143ff:3986:15)
    at BrowserRouter (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=087143ff:543:5)
    at App

CodeSandbox is here

To reproduce, just create the following Components. App.tsx:

import { BrowserRouter } from 'react-router-dom'
import MyRoutes from './MyRoutes';

function App() {
  return (
    <BrowserRouter>
      <MyRoutes />
    </BrowserRouter>
  );
}

export default App

MyRoutes.tsx:

import { Route, Routes, useLocation } from "react-router"
import Component1 from "./Component1";
import { AnimatePresence } from "framer-motion";
import Component2 from "./Component2";

function MyRoutes() {
  const location = useLocation();
  return (
    <AnimatePresence mode="popLayout">
      <Routes location={location} key={location.pathname}>
        <Route path="/first" element={<Component1 />} />
        <Route path="/second" element={<Component2 />} />
      </Routes>
    </AnimatePresence>
  );
}

export default MyRoutes;

Component1.tsx:

import { motion } from "framer-motion";
import { Link } from "react-router-dom";

function Component1() {
  return (
    <motion.div
      initial={{ x: 100 }}
      animate={{ x: 0 }}
      exit={{ x: -100 }}
      transition={{
        duration: 2,
      }}
    >
      <Link to="/second">First</Link>
    </motion.div>
  );
}

export default Component1;

Component2.tsx:

import { motion } from "framer-motion";
import { Link } from "react-router-dom";

function Component2() {
  return (
    <motion.div
      initial={{ x: 100 }}
      animate={{ x: 0 }}
      exit={{ x: -100 }}
      transition={{
        duration: 2,
      }}
    >
      <Link to="/first">Second</Link>
    </motion.div>
  );
}

export default Component2;

I would expect the exiting component to "pop out" of the layout so it doesn't effect the new component coming in.

This in a Vite + Typescript project.

Video:

https://github.com/framer/motion/assets/74564699/10936eda-fada-4bae-901c-678fcb5d4e49

ryan-weisenberger avatar Jan 30 '24 00:01 ryan-weisenberger