{"record":{"id":"ad675ea4ad89b953","repo":"janhq/jan","slug":"tool-components-must-be-used-within-tool","errorCode":null,"errorMessage":"Tool components must be used within Tool","messagePattern":"Tool components must be used within Tool","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web-app/src/components/ai-elements/tool.tsx","lineNumber":57,"sourceCode":"import { parseCitationsFromToolOutput } from '@/lib/citation-parser'\n\n/** Payloads shorter than this fit the collapsed box, so no expand control. */\nconst OUTPUT_EXPAND_THRESHOLD = 600\n\ntype ToolContextValue = {\n  isOpen: boolean\n  setIsOpen: (open: boolean) => void\n  state: ToolUIPart['state']\n  toolCallId?: string\n  messageId?: string\n}\n\nconst ToolContext = createContext<ToolContextValue | null>(null)\n\nexport const useTool = () => {\n  const context = useContext(ToolContext)\n  if (!context) {\n    throw new Error('Tool components must be used within Tool')\n  }\n  return context\n}\n\nexport type ToolProps = ComponentProps<typeof Collapsible> & {\n  className?: string\n  state: ToolUIPart['state']\n  toolCallId?: string\n  messageId?: string\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\nexport const Tool = memo(\n  ({\n    className,\n    state,","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/components/ai-elements/tool.tsx#L39-L75","documentation":"useTool reads ToolContext, which is only populated by the Tool component in tool.tsx. If the hook runs outside any Tool ancestor, useContext returns null and the hook throws. This guards against rendering Tool sub-components (header, content, input, toolbar) standalone.","triggerScenarios":"Rendering <ToolHeader>, <ToolContent>, <ToolInput>, <ToolToolbar> or calling useTool() without a wrapping <Tool> component (e.g. in a Storybook story, test, or custom tool UI).","commonSituations":"Refactoring that moves a tool sub-component out of its Tool parent; composing tool UI inside a chat message renderer that forgot the Tool wrapper; tests rendering sub-components in isolation.","solutions":["Wrap the sub-components in a <Tool toolCallId={...} ...> collapsible before rendering them.","Remove the stray useTool-consuming component if the Tool wrapper is no longer needed.","In tests/stories, add a minimal Tool provider wrapper as the decorator.","If a truly standalone display is needed, avoid these components and build custom markup."],"exampleFix":"// before\n<ToolHeader type={part.type} state={part.state} />\n// after\n<Tool toolCallId={part.toolCallId}>\n  <ToolHeader type={part.type} state={part.state} />\n</Tool>","handlingStrategy":"type-guard","validationCode":"const canRenderToolParts = (el: React.ReactNode) =>\n  React.isValidElement(el) &&\n  (el.type === Tool || isDescendantOfTool(el))","typeGuard":"const toolContext = useContext(ToolContext)\nif (toolContext === null) {\n  // not inside <Tool>; render fallback or skip\n  return null\n}","tryCatchPattern":"try {\n  const ctx = useTool() // note: hooks cannot be wrapped in try/catch; guard instead\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Tool components')) {\n    return <FallbackToolView />\n  }\n  throw e\n}","preventionTips":["Always render Tool sub-components inside <Tool>.","Add Storybook/test decorators that supply a Tool wrapper.","Centralize tool UI composition in one component that owns the Tool wrapper.","Never call useTool in components rendered outside the tool tree (portals keep context; separate roots don't)."],"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"}