FloatingTree
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 parentFloatingNodeFloatingNodeid, if it exists. This will benullnullfor roots (not nested).useFloatingTree()useFloatingTree()for accessing the tree object, which includes an event emitter to communicate across the tree components (eventsevents).
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} />;
}