FloatingTree
Provides context for nested floating elements when they are not children of each other on the DOM.
If your elements are not portalled into a common node (e.g. FloatingPortal), then the natural DOM tree hierarchy renders this unnecessary.
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-dom-interactions';
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 />
component. Usage of this component must be wrapped within a
single <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 elements in a `<FloatingNode />`,
// passing in the subscribed `nodeId`
return (
<FloatingNode id={nodeId}>
{/* reference element */}
<FloatingPortal>
{open && /* floating element */}
</FloatingPortal>
</FloatingNode>
);
}
Hooks
useFloatingNodeId()
subscribes the component to the tree context. Call this only once as it has side effects.useFloatingParentNodeId()
returns the parentFloatingNode
id, if it exists. This will benull
for roots (not nested).useFloatingTree()
for accessing the tree object, which includes an event emitter to communicate across the tree components (events
).
interface FloatingTreeType {
nodesRef: React.MutableRefObject<Array<FloatingNodeType>>;
events: FloatingEvents;
addNode: (node: FloatingNodeType) => void;
removeNode: (node: FloatingNodeType) => void;
}
FloatingTree wrapper
You can use the following technique to avoid having the consumer
specify the <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} />;
}