When building React “action slot” containers (inline vs overflow menus, responsive headers, hold/click controls), enforce these invariants:
1) Preserve host mount semantics
children in multiple parallel trees just to measure layout.2) Preserve DOM identity for event guarantees
document).3) Normalize host output before applying asChild/DOM-level props
<>...</> Fragments (or nested Fragments), flatten/normalize to concrete elements before wrapping with <DropdownMenuItem asChild> (or before cloning/annotating).4) Use the menu framework’s item components for accessibility + behavior
DropdownMenuContent. Wrap each action with the framework’s item component (e.g., DropdownMenuItem asChild) so roles/keyboard navigation and menu auto-close work consistently.5) Don’t rely on custom components forwarding unknown props
cloneElement to inject data-* or handlers unless you can guarantee the host component forwards unknown props via ...rest.<span style={{display:'contents'}} data-...>...</span>) so your attributes always land in the DOM.Example pattern (flatten + menu items + safe wrappers, no Fragment targets):
import { Children, Fragment, isValidElement, useMemo } from 'react';
function flattenToElements(node: React.ReactNode) {
const out: React.ReactElement[] = [];
for (const child of Children.toArray(node)) {
if (!isValidElement(child)) continue;
if (child.type === Fragment) {
out.push(...flattenToElements((child.props as any).children));
} else {
out.push(child);
}
}
return out;
}
function OverflowMenu({ children }: { children: React.ReactNode }) {
const elements = useMemo(() => flattenToElements(children), [children]);
return (
<DropdownMenuContent>
{elements.map((el, i) => (
<DropdownMenuItem key={el.key ?? i} asChild>
<span style={{ display: 'contents' }} data-action-index={String(i)}>
{el}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
);
}
Applying these rules prevents: duplicated subscriptions (double mount), broken menu roles/keyboard behavior (raw children), invalid DOM prop application to Fragments, click-proxy failures from missing prop forwarding, and interaction bugs from DOM identity changes during state transitions.