Base UI MenuGroupLabel 放错位置,点击时整页崩溃
给下拉菜单加一个分组标题,看起来只是多写一行 <MenuGroupLabel>,TypeScript 编译通过,页面初始渲染也正常。结果一点击触发器——整页直接崩溃,跳到 error boundary,框架错误页全屏弹出。
排查了半天 onClick 逻辑,最后发现问题根本不在业务代码。
现象
控制台抛出:
Base UI: MenuGroupRootContext is missing.
Menu group parts must be used within <Menu.Group>.
报错时机极其迷惑:触发器按钮正常渲染,鼠标悬浮也没事,只有真正点开菜单的瞬间才崩。TypeScript 类型检查、SSR、截图测试全部绿灯,直到真实交互才暴露。
根因
MenuGroupLabel 在内部通过 React Context 读取由 MenuGroup 注入的 MenuGroupRootContext。如果 MenuGroupLabel 直接写在 MenuContent 下面,没有 MenuGroup 作为上层 Provider,Context 值就是 undefined——组件在渲染时立刻抛出,整个菜单子树被卸载。
这属于一类典型的"渲染时没事、交互时崩"的 bug:菜单弹层在触发前根本没有挂载,Context 缺失的问题被延迟到第一次打开时才显现。
把 <MenuGroupLabel> 作为 <MenuContent> 的直接子元素,TypeScript 不会报错,初始页面也不报错,但点击菜单时会立即抛出 MenuGroupRootContext is missing,整个应用进入错误状态。
同样的规则适用于所有依赖 Group Context 的 *Group* 系列部件(如 radio group 子项等)。
正解
把标签和对应的菜单项一起包裹进 <MenuGroup>:
// ❌ 点击时崩溃
<MenuContent>
<MenuGroupLabel>移动到</MenuGroupLabel>
{stages.map((s) => (
<MenuItem key={s} onClick={() => move(s)}>{s}</MenuItem>
))}
</MenuContent>
// ✅ 正确写法
<MenuContent>
<MenuGroup>
<MenuGroupLabel>移动到</MenuGroupLabel>
{stages.map((s) => (
<MenuItem key={s} onClick={() => move(s)}>{s}</MenuItem>
))}
</MenuGroup>
</MenuContent>不需要分组标签的普通菜单不必加 MenuGroup,只在用到 MenuGroupLabel 或其他依赖 Group Context 的部件时才需要这层包裹。
只要用到 MenuGroupLabel,外面就套一层 <MenuGroup>。这是 Base UI(@base-ui-components/react)的组合约定,库封装层(如 @hulian/ui 等基于 Base UI 二次封装的组件库)同样适用。
验证要点
修完之后,必须真正点开菜单才算验收通过。有两个常见陷阱:
element.click()或evaluate注入点击往往无效——Base UI 的菜单监听的是 pointer 事件,合成点击事件不一定能触发。要用 Playwright 的click()、testing-library 的userEvent.click(),或 Chrome DevTools MCP 的真实点击工具。- 截图 / SSR 验证不够——触发器渲染正常,崩溃只在交互时发生,截图永远看不到。
一句话外卖
Base UI 的
*Group*系列部件依赖父级注入的 Context,缺少 Provider 只在交互时崩,TypeScript 和静态渲染都不报错——凡用分组标签,必须套 MenuGroup。