-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Note
Motion for Vue issues: Please open in the Motion for Vue repo.
1. Read the FAQs 👇
2. Describe the bug
When switching children inside AnimatePresence rapidly (triggering updates before the previous exit animation completes), exiting children frequently get stuck in the DOM.
Specific observations:
- Stuck Elements: The "Child count" remains greater than 1 even after visual animations cease.
- Incorrect Exit State: The non-active (stuck) children seem to apply the exit state of the currently active child's variant.
- Callback Failure:
onExitCompletefails to fire for these stuck elements. - Recovery: The state only recovers (DOM clears,
onExitCompletefires) after specific subsequent triggers, though the exact trigger point is inconsistent.
Context: This issue seems prominent when using dynamic variants where the animated properties change between renders (e.g., Variant A uses opacity, while Variant B uses transform).
3. IMPORTANT: Provide a CodeSandbox reproduction of the bug
https://codesandbox.io/p/sandbox/7jcg3l
I have included debugging tools in the UI ("Child count" indicator and "Spam" simulation buttons) to make reproduction easier.
4. Steps to reproduce
Steps to reproduce the behavior:
- Open the provided CodeSandbox link.
- Click the "Spam Move 4x" button (this simulates 4 clicks with 100ms intervals) OR manually spam click the "Move" button rapidly.
- Observe the "Child count" indicator in the UI.
- See error: The "Child count" shows a number greater than
1(e.g., 2 or 3) after the animation visually finishes, indicating children are stuck in the DOM. - Check the log: No new
onExitCompletelogs are generated during the bugged state.
5. Expected behavior
- The "Child count" should always return to
1after the transition sequence completes. onExitCompleteshould fire reliably for every exiting component.- Exiting children should be removed from the DOM regardless of how fast the state updates occur.
6. Video or screenshots
Video 1: Bug Reproduction
https://github.com/user-attachments/assets/bb7f4ca4-b5f5-49c0-9919-711d85d2c0d7
Description: I trigger the bug using "Spam Move 4x". The child count remains stuck. I then click "Move" slowly until the system recovers (count returns to 1 and logs appear), then I trigger the bug again.
Video 2: Normal Behavior
https://github.com/user-attachments/assets/4a26eae3-ac9e-488b-9f0f-0ca1a4ce24ce
7. Environment details
CodeSandbox Environment:
- React: 18.2.0
- Motion: 12.33.0
Local Environment (Reproduction confirmed here too):
- OS: Windows 11 Home 24H2
- Browser: Zen (Firefox based) & Microsoft Edge
- React: 19.2.3
- Motion: 12.29.2
FAQs
React Server Components "use client" error
If you're importing motion or m into a React Server Component environment, ensure you're importing from motion/react-client instead of motion/react.
import * as motion from "motion/react-client"
import * as m from "framer-motion/react-m"Motion for React won't install
Different versions of Motion for React are compatible with different versions of React.
React 19: framer-motion@12.0.0-alpha.0 or higher
React 18: framer-motion@7.0.0 to framer-motion@11.x, or motion
React 17: framer-motion@6.x or lower
height: "auto" is jumping
Animating to/from auto requires measuring the DOM. There's no perfect way to do this and if you have also applied padding to the same element, these measurements might be wrong.
The recommended solution is to move padding to a child element. See this issue for the full discussion.
Preact isn't working
Motion for React isn't compatible with Preact.
AnimatePresence isn't working
Have all of its immediate children got a unique key prop that remains the same for that component every render?
// Bad: The index could be given to a different component if the order of items changes
<AnimatePresence>
{items.map((item, index) => (
<Component key={index} />
))}
</AnimatePresence>// Good: The item ID is unique to each component
<AnimatePresence>
{items.map((item, index) => (
<Component key={item.id} />
))}
</AnimatePresence>Is the AnimatePresence correctly outside of the controlling conditional? AnimatePresence must be rendered whenever you expect an exit animation to run - it can't do so if it's unmounted!
// Bad: AnimatePresence is unmounted - exit animations won't run
{
isVisible && (
<AnimatePresence>
<Component />
</AnimatePresence>
)
}// Good: Only the children are unmounted - exit animations will run
<AnimatePresence>{isVisible && <Component />}</AnimatePresence>