DEV Community

Regina Liu
Regina Liu

Posted on

How AnimatePresence in framer-motion works

Suppose you want to have a React component perform an animation before it exits. It's tricky, because there's no simple native way to do so. A component is either in the React tree or not and there's no in-between. It also does not have the knowledge of when exactly it gets removed from the tree prior to its removal.

An intuitive, non-Reacty way is to (1) start the exit animation, (2) wait till the animation is done, then (3) remove the element from the DOM. That's kind of a chore, isn't it? It's probably also not declarative1 enough for our tastes, so perhaps this is where external libraries are able to step in and create a workaround.

The two most popular choices now (circa Jan 2024) are React Transition Group, started in 2016, and Framer Motion, started in 2018. I'm not too familiar with the former, so this article solely dives into the workings of AnimatePresence from Framer Motion and how it's able to enable exit animations.

A brief introduction to AnimatePresence

AnimatePresence allows components to animate out when they're removed from the React tree.2

This is what it looks like in code:

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

export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    )}
  </AnimatePresence>
)
Enter fullscreen mode Exit fullscreen mode

We can see that <motion.div> is controlled by a conditional, isVisible, while AnimatePresence is an immediate wrapper component around its children with an exiting animation.

The exit animation is triggered when isVisible becomes false. Theoretically, <motion.div> should have been removed from the React tree - yet logically it doesn't make sense, since at the point of its "removal", <motion.div> is still performing its exit animation, so that means it still is somehow within the DOM!

Here's where <AnimatePresence> does something sneaky.

The secret sauce: React.useRef

Did someone say "give me a tool that reliably stores existing state during re-renders and does not cause a component re-render when its state is updated"? React.useRef does just that.

We also want to be able to control when exactly children within AnimatePresence re-render to minimize unnecessary overhead, so choosing useRef instead of useState comes in handy here.

With that in mind, here's how framer-motion implemented AnimatePresence:

Imagine there's reality and alternate reality. Picture React's version of the DOM tree as reality, and framer-motion's version as alternate reality. Because we want to enhance our React codebase with exiting animations, let's call this 'alternate reality' solely for the purpose of this article. 'Alternate reality' is what we want to show to users of our app, or whatever we're building, so that they see the fancy-schmancy version instead of the dull, old vanilla version of things.

Two states comes into play in our alternate reality:

  1. currentChildren: elements that are in the React tree - our source of truth
  2. allChildren: elements that are in the React tree (i.e. currentChildren) + elements that are exiting the DOM

Suppose that we have Child1 and Child2 wrapped within AnimatePresence, and none of them are exiting the DOM yet. Here's what the above two states like would look in framer-motion:

Before any exit animations

Next, imagine if Child2 needs to be removed and we need to trigger its exit animation.

React updates currentChildren once Child2 is removed, showing that only Child1 remains. From this, we can derive a third state: exitingChildren, which takes the difference between allChildren and currentChildren.

This third state, exitingChildren, is crucial. Firstly, we want to trigger the exit animations once an element is registered as 'exiting'. Secondly, we want to ensure that this particular element is deleted from AnimatePresence so that we don't keep unnecessary references to elements that don't exist in the React tree.

Child2 being removed from React tree, right before it exits the tree

This is what the final state looks like after Child2 has completely exited the DOM: AnimatePresence does not maintain a trace to it anymore.

Child2 finishes its exit animation

So there you have it - it's a bit of React trickery behind the scenes! The framer-motion state names have been altered for the purposes of this article, but this distills the essence of how AnimatePresence works.

P.s. while reading the implementation of AnimatePresence, I've noticed a bug which caused some components to not exit in sequence. The PR for fixing this has been submitted and is awaiting review.


  1. What is the difference between declarative and imperative paradigm in programming? 

  2. AnimatePresence in Motion docs 

Top comments (5)

Collapse
 
thesanjeevsharma profile image
Sanjeev Sharma

Wow! Such an insightful article. You should write more often.

I am curious, what made you look the framer-motion codebase? Also, were you able to understand it all by yourself?

Collapse
 
regexyl profile image
Regina Liu

Thanks! It's my first ever article - feel free to suggest anything that I could improve upon - or anything that you'd like to see 😄

I used framer-motion at work, and was intrigued by how it was implemented behind the scenes. I do that often enough if I have the time, either by going on GitHub and clicking into the source code, or (in VS Code), right click on the variable + Go to source definition. It's always nice to see how things deviate from what you've imagined of them. It takes quite a bit of time - for this framer-motion codebase, it took me a few hours, but I could have cut the time down by getting less sidetracked to figure out non-essential information to AnimatePresence.

Collapse
 
thesanjeevsharma profile image
Sanjeev Sharma

I'd love to see some articles on frontend performance or about your experience at Meta.

Thread Thread
 
regexyl profile image
Regina Liu

Sounds right up my alley - will be writing more in the next few weeks!

Collapse
 
random_ti profile image
Random

Awesome 🔥