Search code examples
javascriptreactjsframer-motion

React Framer-motion AnimatePresence exit animation does not work


I am trying to animate some page transitions, but for some reason only the entering animation works.

The text slides in properly, but it does not slide out when routing to another component, it just disappears instead. I have tried assigning the keys to individual components using location.pathname and also to the Outlet component, adding exitBeforeEnter in AnimatePresence and mode="wait", so i have no idea what else could be wrong. The main component:

import React from "react";
import Navbar from './navbar.js';
import { Outlet } from "react-router-dom"
import { AnimatePresence } from "framer-motion"
export default function App() {
    return (
      <div>
        <Navbar/>
        <AnimatePresence>
          <Outlet/>
        </AnimatePresence>
      </div>
    );
}

The pages look like this:

import React from 'react';
import './index.css'
import { motion } from "framer-motion"
import { useLocation } from 'react-router-dom';
export default function Section1() {
  let location = useLocation()
    return (
      <motion.p
      key={location.pathname}
      transition={{duration: 0.8, ease: "easeOut"}}
      initial={{ x: "-100%", opacity: 0 }}
      animate={{ x: 0, opacity: 1 }}
      exit={{ x: "100%", opacity: 0 }} className='text-center text-6xl my-56'>Section 1</motion.p>
    )
}

Navbar:

import React from "react"
import { Link } from "react-router-dom";
export default function Navbar(router) {
    return (
      <nav className='bg-gray-300 flex items-center justify-between'>
        <p className="ml-6 text-3xl">Варвара Алексеева</p>
        <ul className='flex flex-row justify-end mr-20 text-xl'> 
          <li className='m-4'><Link to={"/"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Main</Link></li>
          <li className='m-4'><Link to={"/section1"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section 1</Link></li>
          <li className='m-4'><Link to={"/section2"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section-2 2</Link></li>
          <li className='m-4'><Link to={"/section3"} className='hover:bg-lapis hover:text-white p-2 rounded-xl duration-300'>Section 3</Link></li>
        </ul>
      </nav>
    )
  }

Solution

  • While there does not seem to be official guides about this, I think the reason could be Outlet wraps the elements for nested routes with another component (Provider), causing AnimatePresence unable to work, since these elements are no longer direct children of it.

    As an experimental solution, the exit animation seems to work by using useOutlet to manually render the nested routes, and pass a unique key with cloneElement to the element for the route.

    Live demo of the experiment: stackblitz (omitted styles for simplicity)

    import { useLocation, useOutlet } from 'react-router-dom';
    import { AnimatePresence } from "framer-motion"
    
    export default function App() {
      const { pathname } = useLocation();
      const element = useOutlet();
      return (
        <div className="App">
          <Navbar />
          <AnimatePresence mode="wait">
            {element && React.cloneElement(element, { key: pathname })}
          </AnimatePresence>
        </div>
      );
    }