Motion: AnimatePresence exit not working for me...

Created on 27 Mar 2020  ·  15Comments  ·  Source: framer/motion

Dunno if this is a bug or if i'm doing something wrong, but the exit prop does not seem to be working for me. Here is my code:

<AnimatePresence exitBeforeEnter>
  <Container
    {...props}
    initial={{
      opacity: 0
    }}
    animate={{
      opacity: 1,
      transition: { 
        delay: 3,
        duration: 5
      }
    }}
    exit={{
      opacity: 0,
      transition: { 
        delay: 3,
        duration: 5
      }
    }}
  >
    {props.children}
  </Container>
</AnimatePresence>

...based on the code above, i am expecting the component to wait 3 seconds, then fade out over 5 seconds, then wait 3 seconds, then fade in over five seconds.

what actually happens is that the component fades out instantly, then about six seconds pass, then it fades in over 5 seconds...so, the piece that does not seem to be working is the fade out over 5 seconds as the component exits...

do you think this might be a bug or m i doing something incorrect here?

bug

Most helpful comment

@amcc The direct child(ren) of AnimatePresence need to be either changing key or mounting/unmounting to fire exit animations. Here, the direct child is Container, which never does either. So this isn't going to work.

Additionally, AnimatePresence needs to be rendered consistently to track changes to its children. This is an unavoidable constraint of React. Although Transition here is being rendered by Layout in a consistent way, Layout itself is being mounted/unmounted every render. You can see this by adding a useEffect:

const Layout = props => {
  React.useEffect(() => {
    console.log("mount")
  }, [])

This will fire every time a page changes. This means that each AnimatePresence component is an entirely new instance of the component, so it has no connection to any other AnimatePresence components that may have been rendered in the past.

Your usage in the example above is correct - as long as Transition itself isn't remounting. Which it seems to be here. I don't know enough about the Gatsby template system to know if there's a way around this.

All 15 comments

Can you provide a codesandbox I could take a look at? At first glance the
component never looks like it gets removed so I’d only expect the initial
-> animate animation to work

On Fri, 27 Mar 2020 at 07:00, rchrdnsh notifications@github.com wrote:

Dunno if this is a bug or if i'm doing something wrong, but the exit prop
does not seem to be working for me. Here is my code:


{...props}
initial={{
opacity: 0
}}
animate={{
opacity: 1,
transition: {
delay: 3,
duration: 5
}
}}
exit={{
opacity: 0,
transition: {
delay: 3,
duration: 5
}
}}
>
{props.children}

...based on the code above, i am expecting the component to wait 3
seconds, then fade out over 5 seconds, then wait 3 seconds, then fade in
over five seconds.

what actually happens is that the component fades out instantly, then
about six seconds pass, then it fades in over 5 seconds...so, the piece
that does not seem to be working is the fade out over 5 seconds as the
component exits...

do you think this might be a bug or m i doing something incorrect here?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/framer/motion/issues/493, or unsubscribe
https://github.com/notifications/unsubscribe-auth/AB34WKTPZN3YWZNUHFDPCIDRJQ6HPANCNFSM4LUYJDKA
.

hmmmm...as I make a sandbox (which will take me a hot minute) I can give you a little more context...

It’s a Gatsby app with persistent navigation (Gatsby-plugin-layout) and I am using this component as a wrapper for each ‘page’ which automatically gets turned into a route by Gatsby.

So, the main layout component never un-mounts, which includes the navigation. In that layout component I have a ‘main’ component in which each ‘page’ is loaded into when the route changes.

This component is the wrapper around the page (imported and defined in each page, rather than in the layout component) so my assumption is that by wrapping each page with this component the component will un-mount and then re-mount with a new instance tied to the new page as the route is changed, but clearly it is not working as intended, based upon my mental model...

code sandbox to follow...thanks 😁

Any updates on this?

@bramvdpluijm Not without a Codesandbox reproduction

yeah @bramvdpluijm, gotta eek out a little time for a repro, then @InventingWithMonster can take a look...apologies for the delay :-)

i've got this exact issue, i'll try and make a codesandbox tomorrow if i can, but if anyone has any ideas here's my code. This is for a transition component that wraps my layout like this:

<Transition {...props}>
  <Container>
    <main>{props.children}</main>
  </Container>
</Transition>

Here's the transition.js component. I've checked that my location keys are unique if that helps

import React from "react"
import { motion, AnimatePresence } from "framer-motion"

const Transition = ({ children, location }) => {
  const duration = 0.35

  const variants = {
    initial: {
      opacity: 0,
    },
    enter: {
      opacity: 1,
      transition: {
        duration: duration,
        delay: duration,
        when: "beforeChildren",
      },
    },
    exit: {
      opacity: 0,
      transition: { duration: duration },
    },
  }

  return (
    <AnimatePresence>
      <motion.div
        key={location.pathname}
        variants={variants}
        initial="initial"
        animate="enter"
        exit="exit"
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

export default Transition

framer motion version: 1.10.3

I've made a CodeSandBox here:
https://codesandbox.io/s/github/amcc/transition-box-debug

A repo is here:
https://github.com/amcc/transition-box-debug

Its a Gatsby site

There's a transition.js file in src/components/theming/transition.js
This file is as above in the previous comment

This is wrapping elements in src/components/layout.js
There's a snippet of that in my previous comment.

I hope you can spot something i've done wrong or help out in some way - if i need to provide anything else let me know, thanks.

@amcc The direct child(ren) of AnimatePresence need to be either changing key or mounting/unmounting to fire exit animations. Here, the direct child is Container, which never does either. So this isn't going to work.

Additionally, AnimatePresence needs to be rendered consistently to track changes to its children. This is an unavoidable constraint of React. Although Transition here is being rendered by Layout in a consistent way, Layout itself is being mounted/unmounted every render. You can see this by adding a useEffect:

const Layout = props => {
  React.useEffect(() => {
    console.log("mount")
  }, [])

This will fire every time a page changes. This means that each AnimatePresence component is an entirely new instance of the component, so it has no connection to any other AnimatePresence components that may have been rendered in the past.

Your usage in the example above is correct - as long as Transition itself isn't remounting. Which it seems to be here. I don't know enough about the Gatsby template system to know if there's a way around this.

Thats really helpful thanks, so i can set a key using the pathurl on the Container, thats easy enough, but you're right the layout.js is being mounted on every page. This certainly isn't a bug and thanks for the help.

There's a route to fix this which i've seen in this Gatsby theme: https://www.gatsbyjs.org/packages/gatsby-theme-amsterdam/ (which gave me the idea and much of the method in the first place). They are using this wrapPageElement function to wrap the page element to prevent layout getting unmounted on page changes:
https://www.gatsbyjs.org/docs/browser-apis/#wrapPageElement

I'll try and implement that now and put it up on the codesandbox / github to help others. Thanks for taking the time for the great suggestions above. I can see the issue now!

Fixed it and the have updated the repo and sandbox site with the changes, all working now.

To do this yourself add this kind of thing to your gatsby-browser.js:

import CustomLayout from "./src/gatsby/browser/wrapPageElement"
export const wrapPageElement = CustomLayout

then in your wrapPageElement.js you need this

import React from "react"
import Layout from "./../../components/layout"

const CustomLayout = ({ element, props }, pluginOptions) => {
  return <Layout {...props}>{element}</Layout>
}

export default CustomLayout

after that you can remove the Layout component that wraps any pages/templates in gatsby and replace it with a fragment <> </>

Works a charm

incidentally it animates fine on exit without giving the Container component a key as @InventingWithMonster mentioned above - you can try removing that in the sandbox. But i've left it in anyway, I may be missing something vital here.

Awesome work! Thanks for updating us with a solution. You can probably ditch the key. It's mostly for when you're reusing the same child and want to animate the "old" one out and the new one in, or when animating multiple children.

Makes sense about the Container key. Thanks for the support, I needed that little hint! Framer is fantastically intuitive and a joy to use btw :)

apologies, I haven't had a chance to come back around to this, but I ended implementing this with react transition group, but I will come back around to this when i have a chance and see if there are any changes I can make to the framer motion version based on what I learned doing it with RTG to make the framer-motion one work for me as well...I am already using gatsby-plugin-layout, but I'll take a look as well and see if manually adding the wrap page is a better approach in my case as well.

you are doing something wrong.
AnimatePresence will detect which children has been mounted or unmounted.
You have Container that is ALWAYS rendered as a children. It will never animate because Container is always mounted; it never unmounts, so you wont get any animation

On top of that, you also have to give "key" prop to each children, so that AnimatePresence can detect them.

This can be closed?

Was this page helpful?
0 / 5 - 0 ratings