Floating Tree
Provides context for nested floating elements when they are not children of each other on the DOM.
This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for:
- The
bubbles
option in theuseDismiss()
Hook - Nested virtual list navigation
- Nested floating elements that each open on hover
- Custom communication between parent and child floating elements
Usage
The following creates an infinitely nestable <Popover>
component. Usage of this component must be wrapped in a single
<FloatingTree>
provider:
import {
FloatingTree,
FloatingNode,
useFloatingNodeId,
} from '@floating-ui/react';
function Popover({children, content}) {
const [isOpen, setIsOpen] = useState(false);
// Subscribe this component to the <FloatingTree> wrapper:
const nodeId = useFloatingNodeId();
// Pass the subscribed `nodeId` to `useFloating`:
const {refs, floatingStyles} = useFloating({
nodeId,
open: isOpen,
onOpenChange: setIsOpen,
});
// Wrap the rendered floating element in a `<FloatingNode>`,
// passing in the subscribed `nodeId`:
return (
<>
{cloneElement(children, {ref: refs.setReference})}
<FloatingNode id={nodeId}>
{isOpen && (
<FloatingPortal>
<div ref={refs.setFloating}>{content}</div>
</FloatingPortal>
)}
</FloatingNode>
</>
);
}
function App() {
return (
<FloatingTree>
<Popover
content={
<Popover content="Nested content">
<button>Nested reference</button>
</Popover>
}
>
<button>Root reference</button>
</Popover>
</FloatingTree>
);
}
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;
}
Custom parent
By default, the parent node is the closest
<FloatingNode />
, but you can specify a custom parent node
by passing in the parentId
to useFloatingNodeId()
:
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>
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} />;
}
Troubleshooting
Ensure that the component that you’re calling
useFloatingNodeId()
is a child of the
<FloatingTree>
wrapper. If it is not, then it will not be
able to find the tree context.