{"record":{"id":"3f99c8d85698e584","repo":"janhq/jan","slug":"dropdrawer-components-cannot-be-rendered-outside-the","errorCode":null,"errorMessage":"DropDrawer components cannot be rendered outside the DropDrawer Context","messagePattern":"DropDrawer components cannot be rendered outside the DropDrawer Context","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/components/ui/dropdrawer.tsx","lineNumber":35,"sourceCode":"  DropdownMenuItem,\r\n  DropdownMenuLabel,\r\n  DropdownMenuSeparator,\r\n  DropdownMenuSub,\r\n  DropdownMenuSubContent,\r\n  DropdownMenuSubTrigger,\r\n  DropdownMenuTrigger,\r\n} from \"./dropdown-menu\";\r\nimport { useIsMobile } from \"@/hooks/use-mobile\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nconst DropDrawerContext = React.createContext<{ isMobile: boolean }>({\r\n  isMobile: false,\r\n});\r\n\r\nconst useDropDrawerContext = () => {\r\n  const context = React.useContext(DropDrawerContext);\r\n  if (!context) {\r\n    throw new Error(\r\n      \"DropDrawer components cannot be rendered outside the DropDrawer Context\",\r\n    );\r\n  }\r\n  return context;\r\n};\r\n\r\nfunction DropDrawer({\r\n  children,\r\n  ...props\r\n}:\r\n  | React.ComponentProps<typeof Drawer>\r\n  | React.ComponentProps<typeof DropdownMenu>) {\r\n  const isMobile = useIsMobile();\r\n  const DropdownComponent = isMobile ? Drawer : DropdownMenu;\r\n\r\n  return (\r\n    <DropDrawerContext.Provider value={{ isMobile }}>\r\n      <DropdownComponent\r","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/components/ui/dropdrawer.tsx#L17-L53","documentation":"useDropDrawerContext reads DropDrawerContext created by the DropDrawer root component. DropDrawer sub-items (triggers, content, items) rely on that context for open/close state and mobile behavior; without it the hook throws. It prevents rendering dropdrawer pieces outside the root.","triggerScenarios":"Rendering <DropDrawerItem> or other dropdrawer sub-components without an ancestor <DropDrawer>, e.g. inside a portal, modal, or a list that renders items directly.","commonSituations":"Placing a DropDrawerItem inside a Tooltip/Popover portal that escapes the React tree context propagation; forgetting the root when refactoring menus; unit tests rendering items alone.","solutions":["Wrap the dropdrawer sub-components in a <DropDrawer> root component.","Move the sub-components back inside the DropDrawer JSX tree so context propagates.","In tests/stories, wrap with a DropDrawer root before rendering items.","If using React portals, keep the portal created inside the DropDrawer tree (portals still inherit React context)."],"exampleFix":"// before\n<DropDrawerItem onClick={del}>Delete</DropDrawerItem>\n// after\n<DropDrawer>\n  <DropDrawerTrigger>Actions</DropDrawerTrigger>\n  <DropDrawerContent>\n    <DropDrawerItem onClick={del}>Delete</DropDrawerItem>\n  </DropDrawerContent>\n</DropDrawer>","handlingStrategy":"type-guard","validationCode":"const inDropDrawer = () => {\n  const ctx = React.useContext(DropDrawerContext)\n  return ctx !== null\n}","typeGuard":"const ctx = React.useContext(DropDrawerContext)\nif (ctx === null) {\n  // render fallback or no-op instead of using context values\n  return null\n}","tryCatchPattern":"try {\n  renderMenu()\n} catch (e) {\n  if (e instanceof Error && e.message.includes('DropDrawer Context')) {\n    console.warn('DropDrawer item rendered without root; skipping')\n  } else throw e\n}","preventionTips":["Keep all DropDrawerItem/Content inside a <DropDrawer> root.","When portaling content, create the portal inside the DropDrawer tree.","Add a test rendering each menu composition to catch missing roots early."],"tags":["react","context"],"backgroundTag":"null-argument","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}