Floating Tree
Provides context for nested floating elements when they are not children of each other on the DOM.
A nested floating element may look like the following, where there is a popover inside the content of another one:
import {FloatingTree} from '@floating-ui/react';
function App() {
return (
<FloatingTree>
<Popover
render={() => (
<Popover render={() => 'Nested Popover'}>
<button>Child</button>
</Popover>
)}
>
<button>Root</button>
</Popover>
</FloatingTree>
);
}
import {FloatingTree} from '@floating-ui/react';
function App() {
return (
<FloatingTree>
<Popover
render={() => (
<Popover render={() => 'Nested Popover'}>
<button>Child</button>
</Popover>
)}
>
<button>Root</button>
</Popover>
</FloatingTree>
);
}
Usage
The following creates an infinitely nestable <Popover />
<Popover />
component. Usage of this component must be wrapped within a
single <FloatingTree />
<FloatingTree />
component as seen in the previous
code snippet:
function Popover({children}) {
// Subscribe this component to the <FloatingTree />
const nodeId = useFloatingNodeId();
// Pass the subscribed `nodeId` to useFloating()
useFloating({
nodeId
});
// Wrap the rendered element(s) in a `<FloatingNode />`,
// passing in the subscribed `nodeId`
return (
<FloatingNode id={nodeId}>
<FloatingPortal>
{isOpen && /* floating element */}
</FloatingPortal>
</FloatingNode>
);
}
function Popover({children}) {
// Subscribe this component to the <FloatingTree />
const nodeId = useFloatingNodeId();
// Pass the subscribed `nodeId` to useFloating()
useFloating({
nodeId
});
// Wrap the rendered element(s) in a `<FloatingNode />`,
// passing in the subscribed `nodeId`
return (
<FloatingNode id={nodeId}>
<FloatingPortal>
{isOpen && /* floating element */}
</FloatingPortal>
</FloatingNode>
);
}
Hooks
useFloatingNodeId()
useFloatingNodeId()
subscribes the component to the tree context. Call this only once as it has side effects.useFloatingParentNodeId()
useFloatingParentNodeId()
returns the parentFloatingNode
FloatingNode
id, if it exists. This will benull
null
for roots (not nested).useFloatingTree()
useFloatingTree()
for accessing the tree object, which includes an event emitter to communicate across the tree components (events
events
).
interface FloatingTreeType {
nodesRef: React.MutableRefObject<Array<FloatingNodeType>>;
events: FloatingEvents;
addNode: (node: FloatingNodeType) => void;
removeNode: (node: FloatingNodeType) => void;
}
interface FloatingTreeType {
nodesRef: React.MutableRefObject<Array<FloatingNodeType>>;
events: FloatingEvents;
addNode: (node: FloatingNodeType) => void;
removeNode: (node: FloatingNodeType) => void;
}
Custom parent
By default, the parent node is the closest
<FloatingNode />
<FloatingNode />
, but you can specify a custom parent node
by passing in the parentId
to useFloatingNodeId()
useFloatingNodeId()
:
const nodeId = useFloatingNodeId(parentId);
const nodeId = useFloatingNodeId(parentId);
This is useful if you want to mark a sibling floating element in the React tree as the child of another sibling.
FloatingTree wrapper
You can use the following technique to avoid having the consumer
specify the <FloatingTree />
<FloatingTree />
wrapper:
function PopoverComponent() {
// Main logic as seen earlier
}
// This is the component the consumer uses
export function Popover(props) {
const parentId = useFloatingParentNodeId();
// This is a root, so we wrap it with the tree
if (parentId === null) {
return (
<FloatingTree>
<PopoverComponent {...props} />
</FloatingTree>
);
}
return <PopoverComponent {...props} />;
}
function PopoverComponent() {
// Main logic as seen earlier
}
// This is the component the consumer uses
export function Popover(props) {
const parentId = useFloatingParentNodeId();
// This is a root, so we wrap it with the tree
if (parentId === null) {
return (
<FloatingTree>
<PopoverComponent {...props} />
</FloatingTree>
);
}
return <PopoverComponent {...props} />;
}