@@ -38,23 +38,24 @@ export const usePortalContext = () => React.useContext(PortalContext);
38
38
const attr = createAttribute ( 'portal' ) ;
39
39
40
40
export interface UseFloatingPortalNodeProps {
41
- id ?: string ;
42
41
root ?: HTMLElement | ShadowRoot | null | React . RefObject < HTMLElement | ShadowRoot | null > ;
43
42
}
44
43
45
44
/**
46
45
* @see https://floating-ui.com/docs/FloatingPortal#usefloatingportalnode
47
46
*/
48
47
export function useFloatingPortalNode ( props : UseFloatingPortalNodeProps = { } ) {
49
- const { id , root } = props ;
48
+ const { root } = props ;
50
49
51
50
const uniqueId = useId ( ) ;
52
51
const portalContext = usePortalContext ( ) ;
53
52
54
53
const [ portalNode , setPortalNode ] = React . useState < HTMLElement | null > ( null ) ;
55
54
56
55
const portalNodeRef = React . useRef < HTMLDivElement | null > ( null ) ;
56
+ const prevContainerRef = React . useRef < HTMLElement | ShadowRoot | null > ( null ) ;
57
57
58
+ // Cleanup when the portal node instance changes or unmounts.
58
59
useIsoLayoutEffect ( ( ) => {
59
60
return ( ) => {
60
61
portalNode ?. remove ( ) ;
@@ -63,81 +64,60 @@ export function useFloatingPortalNode(props: UseFloatingPortalNodeProps = {}) {
63
64
// https://github.com/floating-ui/floating-ui/issues/2454
64
65
queueMicrotask ( ( ) => {
65
66
portalNodeRef . current = null ;
67
+ prevContainerRef . current = null ;
66
68
} ) ;
67
69
} ;
68
70
} , [ portalNode ] ) ;
69
71
72
+ // Handle reactive `root` changes (including undefined <-> container changes)
70
73
useIsoLayoutEffect ( ( ) => {
71
- // Wait for the uniqueId to be generated before creating the portal node in
72
- // React <18 (using `useFloatingId` instead of the native `useId`).
73
- // https://github.com/floating-ui/floating-ui/issues/2778
74
- if ( ! uniqueId ) {
75
- return ;
76
- }
77
- if ( portalNodeRef . current ) {
78
- return ;
79
- }
80
- const existingIdRoot = id ? document . getElementById ( id ) : null ;
81
- if ( ! existingIdRoot ) {
82
- return ;
83
- }
84
-
85
- const subRoot = document . createElement ( 'div' ) ;
86
- subRoot . id = uniqueId ;
87
- subRoot . setAttribute ( attr , '' ) ;
88
- existingIdRoot . appendChild ( subRoot ) ;
89
- portalNodeRef . current = subRoot ;
90
- setPortalNode ( subRoot ) ;
91
- } , [ id , uniqueId ] ) ;
92
-
93
- useIsoLayoutEffect ( ( ) => {
94
- // Wait for the root to exist before creating the portal node. The root must
95
- // be stored in state, not a ref, for this to work reactively.
74
+ // "Wait" mode: remove any existing node and pause until root changes.
96
75
if ( root === null ) {
76
+ if ( portalNodeRef . current ) {
77
+ portalNodeRef . current . remove ( ) ;
78
+ portalNodeRef . current = null ;
79
+ setPortalNode ( null ) ;
80
+ }
81
+ prevContainerRef . current = null ;
97
82
return ;
98
83
}
84
+
85
+ // For React 17, as the id is generated in an effect instead of React.useId().
99
86
if ( ! uniqueId ) {
100
87
return ;
101
88
}
102
- if ( portalNodeRef . current ) {
103
- return ;
104
- }
105
89
106
- let container = root || portalContext ?. portalNode ;
107
- if ( container && ! isNode ( container ) ) {
108
- container = container . current ;
109
- }
110
- container = container || document . body ;
90
+ const resolvedContainer =
91
+ ( root && ( isNode ( root ) ? root : root . current ) ) || portalContext ?. portalNode || document . body ;
92
+
93
+ const containerChanged = resolvedContainer !== prevContainerRef . current ;
111
94
112
- let idWrapper : HTMLDivElement | null = null ;
113
- if ( id ) {
114
- idWrapper = document . createElement ( 'div' ) ;
115
- idWrapper . id = id ;
116
- container . appendChild ( idWrapper ) ;
95
+ if ( portalNodeRef . current && containerChanged ) {
96
+ portalNodeRef . current . remove ( ) ;
97
+ portalNodeRef . current = null ;
98
+ setPortalNode ( null ) ;
117
99
}
118
100
119
- const subRoot = document . createElement ( 'div' ) ;
101
+ if ( portalNodeRef . current ) {
102
+ return ;
103
+ }
120
104
121
- subRoot . id = uniqueId ;
122
- subRoot . setAttribute ( attr , '' ) ;
105
+ const portalElement = document . createElement ( 'div' ) ;
106
+ portalElement . id = uniqueId ;
107
+ portalElement . setAttribute ( attr , '' ) ;
108
+ resolvedContainer . appendChild ( portalElement ) ;
123
109
124
- container = idWrapper || container ;
125
- container . appendChild ( subRoot ) ;
110
+ portalNodeRef . current = portalElement ;
111
+ prevContainerRef . current = resolvedContainer ;
126
112
127
- portalNodeRef . current = subRoot ;
128
- setPortalNode ( subRoot ) ;
129
- } , [ id , root , uniqueId , portalContext ] ) ;
113
+ setPortalNode ( portalElement ) ;
114
+ } , [ root , uniqueId , portalContext ] ) ;
130
115
131
116
return portalNode ;
132
117
}
133
118
134
119
export interface FloatingPortalProps {
135
120
children ?: React . ReactNode ;
136
- /**
137
- * Optionally selects the node with the id if it exists, or create it and
138
- * append it to the specified `root` (by default `document.body`).
139
- */
140
- id ?: string ;
141
121
/**
142
122
* Specifies the root node the portal container will be appended to.
143
123
*/
@@ -160,9 +140,9 @@ export interface FloatingPortalProps {
160
140
* @internal
161
141
*/
162
142
export function FloatingPortal ( props : FloatingPortalProps ) : React . JSX . Element {
163
- const { children, id , root, preserveTabOrder = true } = props ;
143
+ const { children, root, preserveTabOrder = true } = props ;
164
144
165
- const portalNode = useFloatingPortalNode ( { id , root } ) ;
145
+ const portalNode = useFloatingPortalNode ( { root } ) ;
166
146
const [ focusManagerState , setFocusManagerState ] = React . useState < FocusManagerState > ( null ) ;
167
147
168
148
const beforeOutsideRef = React . useRef < HTMLSpanElement > ( null ) ;
@@ -211,10 +191,7 @@ export function FloatingPortal(props: FloatingPortalProps): React.JSX.Element {
211
191
} , [ portalNode , preserveTabOrder , modal ] ) ;
212
192
213
193
React . useEffect ( ( ) => {
214
- if ( ! portalNode ) {
215
- return ;
216
- }
217
- if ( open ) {
194
+ if ( ! portalNode || open ) {
218
195
return ;
219
196
}
220
197
enableFocusInside ( portalNode ) ;
0 commit comments