Skip to content

Commit 8d0affc

Browse files
authored
feat: add useFloatingPortalNode hook (floating-ui#1672)
1 parent e52eea7 commit 8d0affc

File tree

3 files changed

+54
-16
lines changed

3 files changed

+54
-16
lines changed

packages/react-dom-interactions/src/FloatingPortal.tsx

+32-15
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,17 @@ import useLayoutEffect from 'use-isomorphic-layout-effect';
44

55
const DEFAULT_ID = 'floating-ui-root';
66

7-
/**
8-
* Portals your floating element outside of the main app node.
9-
* @see https://floating-ui.com/docs/FloatingPortal
10-
*/
11-
export const FloatingPortal = ({
12-
children,
7+
export const useFloatingPortalNode = ({
138
id = DEFAULT_ID,
14-
root = null,
9+
enabled = true,
1510
}: {
16-
children?: React.ReactNode;
1711
id?: string;
18-
root?: HTMLElement | null;
19-
}): React.ReactPortal | null => {
20-
const [mounted, setMounted] = React.useState(false);
12+
enabled?: boolean;
13+
} = {}) => {
2114
const portalRef = React.useRef<HTMLElement | null>(null);
2215

2316
useLayoutEffect(() => {
24-
if (root) {
17+
if (!enabled) {
2518
return;
2619
}
2720

@@ -37,16 +30,40 @@ export const FloatingPortal = ({
3730
if (!document.body.contains(portalRef.current)) {
3831
document.body.appendChild(portalRef.current);
3932
}
33+
}, [id, enabled]);
34+
35+
return portalRef.current;
36+
};
4037

38+
/**
39+
* Portals your floating element outside of the main app node.
40+
* @see https://floating-ui.com/docs/FloatingPortal
41+
*/
42+
export const FloatingPortal = ({
43+
children,
44+
id = DEFAULT_ID,
45+
root = null,
46+
}: {
47+
children?: React.ReactNode;
48+
id?: string;
49+
root?: HTMLElement | null;
50+
}): React.ReactPortal | null => {
51+
const [mounted, setMounted] = React.useState(false);
52+
const portalNode = useFloatingPortalNode({id, enabled: !root});
53+
54+
useLayoutEffect(() => {
55+
if (root) {
56+
return;
57+
}
4158
setMounted(true);
42-
}, [id, root]);
59+
}, [root]);
4360

4461
if (root) {
4562
return createPortal(children, root);
4663
}
4764

48-
if (mounted && portalRef.current) {
49-
return createPortal(children, portalRef.current);
65+
if (mounted && portalNode) {
66+
return createPortal(children, portalNode);
5067
}
5168

5269
return null;

packages/react-dom-interactions/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function useFloating<RT extends ReferenceType = ReferenceType>({
9999
export * from '@floating-ui/react-dom';
100100
export {useInteractions} from './useInteractions';
101101
export {safePolygon} from './safePolygon';
102-
export {FloatingPortal} from './FloatingPortal';
102+
export {FloatingPortal, useFloatingPortalNode} from './FloatingPortal';
103103
export {FloatingOverlay} from './FloatingOverlay';
104104
export {FloatingFocusManager} from './FloatingFocusManager';
105105
export {

website/pages/docs/FloatingPortal.mdx

+21
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,24 @@ supports shadow roots.
4949
{open && <div>Tooltip</div>}
5050
</FloatingPortal>
5151
```
52+
53+
## useFloatingPortalNode
54+
55+
Exposes the portal container node for custom use in other
56+
components. The hook injects the node, if it does not yet exist
57+
in the DOM.
58+
59+
```js
60+
function App() {
61+
const portalNode = useFloatingPortalNode({
62+
id: 'custom-root-id', // 'floating-ui-root' by default
63+
enabled: false, // true by default
64+
});
65+
66+
if (portalNode) {
67+
return ReactDOM.createPortal(<div></div>, portalNode);
68+
}
69+
70+
return null;
71+
}
72+
```

0 commit comments

Comments
 (0)