Skip to content

Conversation

@layershifter
Copy link
Member

@layershifter layershifter commented Jan 5, 2026

Previous Behavior

Components that are created via createPresenceComponent() and using unmountOnExit will be retained till a host component will be unmounted.

image
2026-01-05.11.32.19.mp4

Typical scenario

  • Open a Dialog
  • Close a Dialog
  • Call GC, capture snapshot, notice a recent instance on a AnimationHandle being retained even when an element is removed from DOM

New Behavior

If an element is removed from DOM, it's safe to cleanup references. The problem is that AnimationHandle is created by a React.useCallback in useAnimateAtomsInSupportedEnvironment() that references element, it's an actual retainer:

image
  return React.useCallback(
    (
      element: HTMLElement, // 👈 here is a context reference
      // ...
    ): AnimationHandle => {

      return {
        set playbackRate(rate: number) {},
        setMotionEndCallbacks(onfinish: () => void, oncancel: () => void) {}
        // ...
      }
  }, []);

I moved creation of objects to createHandle(), so closures don't have a reference to an element anymore and only keep references to animations. dispose() method introduces to clear references to animations.

.dispose() is called in React.useEffect() once a DOM element is gone, this fixes the issue:

image

P.S. Unit test is not added as it required hijacking internals deeply.

Related Issue(s)

Fixes #35534

@layershifter layershifter force-pushed the fix/retained-obj-on-unmount branch from f9d24ce to 4268b8f Compare January 5, 2026 12:52
@github-actions
Copy link

github-actions bot commented Jan 5, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
107.34 kB
32.856 kB
107.479 kB
32.897 kB
139 B
41 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
237.274 kB
68.562 kB
237.413 kB
68.604 kB
139 B
42 B
react-components
react-components: entire library
1.285 MB
321.877 kB
1.285 MB
321.905 kB
139 B
28 B
react-dialog
Dialog (including children components)
102.818 kB
30.666 kB
102.957 kB
30.715 kB
139 B
49 B
react-motion
@fluentui/react-motion - createMotionComponent()
4.109 kB
1.806 kB
4.156 kB
1.818 kB
47 B
12 B
react-motion
@fluentui/react-motion - createPresenceComponent()
5.771 kB
2.396 kB
5.908 kB
2.442 kB
137 B
46 B
react-toast
Toast (including Toaster)
103.3 kB
30.898 kB
103.439 kB
30.933 kB
139 B
35 B
react-tree
FlatTree
148.374 kB
42.409 kB
148.514 kB
42.452 kB
140 B
43 B
react-tree
PersonaFlatTree
150.2 kB
42.79 kB
150.34 kB
42.836 kB
140 B
46 B
react-tree
PersonaTree
146.261 kB
41.608 kB
146.4 kB
41.665 kB
139 B
57 B
react-tree
Tree
144.441 kB
41.25 kB
144.58 kB
41.292 kB
139 B
42 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
70.269 kB
20.055 kB
react-components
react-components: FluentProvider & webLightTheme
43.608 kB
14.165 kB
react-message-bar
MessageBar (all components)
24.137 kB
8.901 kB
react-motion
@fluentui/react-motion - PresenceGroup
1.727 kB
823 B
react-portal-compat
PortalCompatProvider
8.386 kB
2.624 kB
react-timepicker-compat
TimePicker
109.023 kB
36.011 kB
🤖 This report was generated against 3aace95ca58cea44878c36ad106a6cfcfcecb636

@github-actions
Copy link

github-actions bot commented Jan 5, 2026

Pull request demo site: URL

@layershifter layershifter marked this pull request as ready for review January 5, 2026 13:42
@layershifter layershifter requested review from a team as code owners January 5, 2026 13:42
@layershifter layershifter enabled auto-merge (squash) January 7, 2026 10:31
@layershifter layershifter merged commit 92b783d into microsoft:master Jan 7, 2026
12 checks passed
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.

[Bug]: DialogSurface Detached DOM leak

2 participants