Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

When using DragControls with React Portal leads to memory leaks after unmounting. #2444

Open
shankakode opened this issue Dec 11, 2023 · 0 comments
Labels
bug Something isn't working

Comments

@shankakode
Copy link

1. Read the FAQs 馃憞
Yes.

2. Describe the bug

This is bug that we ran into with 10.10.0 but can be produced with the latest version as well while using React 18.
We have a Modal component (uses React.createPortal) that has animations as well as dragging feature. Since FramerMotion supports dragging, we used the hook useDragControls for the same. The functionality works very fine but when the Modal is unmounted it leads to memory leaks(Framer code having active links to detached dom node). This happens even if you do not do any drag interaction; just attaching dragControls seems to surface the problem

this required cleanup in the following files (per my judgement which may not be the best considering my non familiarity with the code)
a) gestures/drag/VisualElementDragControl.mjs
b) projecton/node/create-projection-node.mjs
c) render/VisualElement.mjs
d) value/index.mjs

3. IMPORTANT: Provide a CodeSandbox reproduction of the bug

A CodeSandbox minimal reproduction will allow us to quickly follow the reproduction steps. Without one, this bug report won't be accepted.

https://codesandbox.io/p/sandbox/simple-toast-with-zustand-framer-motion-forked-rgt8p2

(I have forked one existing sample to highlight the issue with dragControls usage; (Please see Toast.js module and within it the component named Toast to see the impacted code)

4. Steps to reproduce

  1. Use Edge browser (just because it has this detached Elements Tab). You can use Chrome as well but will need to compare the Component mounted and unmounted memory screenshots and filter detached dom nodes to view the retainers)
  2. Open the above mentioned sandbox link
  3. Open Dev tools (F12 key on windows); Use the + button to add the detached Elements tab
  4. Click Collect Garbage button and then the button to get detached nodes (once in the beginning to just check initial state)
  5. Click on Show First Toast button in the sample to see the Toast opened at bottom right corner of browser
  6. Click on Remove First Toast button to remove the Toast
  7. Repeat Step 4 to see the detached dom node
  8. Use the analyze button to capture a memory screen shot. It will also assign an Id to the detached dom node being shown
  9. click on the Id to see the Retainers

5. Expected behavior

There should be no memory leak

6. Video or screenshots

image

7. Environment details

I tried on Windows 10; Edge Version 119.0.2151.97 (Official build) (64-bit). But this can be reproduce in other browsers as well

FAQs

Framer Motion won't install

Framer Motion 7+ uses React 18 as a minimum. If you can't upgrade React, install the latest version of Framer Motion 6.

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.

Type error with AnimateSharedLayout

AnimateSharedLayout was deprecated in 5.0. Refer to the upgrade guide for instructions on how to remove.

Preact isn't working

Framer Motion 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>
@shankakode shankakode added the bug Something isn't working label Dec 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant