Skip to content

Conversation

@seal7766
Copy link

🐛 Bug Fix: Prevent Duplicate Exit Animations in AnimatePresence

📋 Problem

AnimatePresence component can process the same exit animation multiple times when receiving rapid sequential DOM events. This occurs when:

  1. User clicks a dismissable overlay (first pointerdown event)
  2. The overlay's dismissal propagates another event immediately
  3. Both events trigger state updates that call the same component's exit handler
  4. The component attempts to animate out twice, causing visual glitches

This is particularly common with UI libraries like Radix UI, where dismissable layers can fire multiple events in quick succession.

✨ Solution

Track components currently processing exit animations to prevent duplicate handling:

// Add tracking for components in exit process
const exitingComponents = useRef(new Set<ComponentKey>())

const onExit = () => {
    // Skip if already processing this component's exit
    if (exitingComponents.current.has(key)) {
        return
    }

    exitingComponents.current.add(key)

    // Handle edge cases and perform exit...
    // Clean up tracking state when done
}

🔧 How It Works

  1. When onExit is called, check if the component is already being processed
  2. If yes, skip the duplicate call
  3. If no, mark it as processing and continue with exit animation
  4. Clean up the tracking state when:
    • The component finishes exiting
    • The component re-enters before exit completes
    • All exit animations complete

✅ Testing

  • All existing tests pass
  • Manually verified with multiple UI libraries (Radix UI, Arco Design)
  • No performance impact (O(1) Set operations)
  • Works correctly in React StrictMode

🔗 Related Issues


Impact: Low risk, high value fix for production applications using AnimatePresence with modern UI libraries.

…ents

  - Add exit state tracking to prevent processing same component twice
  - Fix issues with Radix UI and other libraries triggering multiple events
  - Clean up tracking state appropriately on completion and re-entry
@mattgperry
Copy link
Collaborator

Thanks for the PR! Is it possible to get a test that fails on main and passes on this branch?

@seal7766
Copy link
Author

@mattgperry
I spent considerable time trying to create a failing test for the main branch:

Unfortunately, I couldn't reproduce the exact bug in tests because it requires:

  • Multiple library versions creating separate React contexts
  • Specific DOM event bubbling that jsdom doesn't fully replicate
  • A race condition that only occurs with real event propagation

While I couldn't provide a failing test, the fix is straightforward defensive
programming that prevents a real issue we've encountered and fixed in production.

Would you thoughts on this approach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants