{"record":{"id":"af55f2f3572cf4c3","repo":"honojs/hono","slug":"cannot-update-an-unmounted-root","errorCode":null,"errorMessage":"Cannot update an unmounted root","messagePattern":"Cannot update an unmounted root","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/jsx/dom/client.ts","lineNumber":41,"sourceCode":"export const createRoot = (\n  element: HTMLElement | DocumentFragment,\n  options: RootOptions = {}\n): Root => {\n  let setJsxNode:\n    | undefined // initial state\n    | ((jsxNode: unknown) => void) // rendered\n    | null = // unmounted\n    undefined\n\n  if (Object.keys(options).length > 0) {\n    console.warn('createRoot options are not supported yet')\n  }\n\n  return {\n    render(jsxNode: unknown) {\n      if (setJsxNode === null) {\n        // unmounted\n        throw new Error('Cannot update an unmounted root')\n      }\n      if (setJsxNode) {\n        // rendered\n        setJsxNode(jsxNode)\n      } else {\n        renderNode(\n          buildNode({\n            tag: () => {\n              const [_jsxNode, _setJsxNode] = useState(jsxNode)\n              setJsxNode = _setJsxNode\n              return _jsxNode\n            },\n            props: {},\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          } as any) as NodeObject,\n          element\n        )\n      }","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/honojs/hono/blob/e2740d5a1bd0b4254e517e3af8b60789284bc7bd/src/jsx/dom/client.ts#L23-L59","documentation":"This error comes from hono/jsx/dom/client's createRoot/hydrateRoot handle. Calling render() on a root whose internal setJsxNode is null (i.e. the root was unmounted or never fully initialized) throws, because there is no live rendering pipeline to update.","triggerScenarios":"Calling root.render(...) after the root was unmounted, or rendering to a root created for an element that was removed from the DOM; holding a stale root handle from a previous page/component lifecycle and reusing it after remount.","commonSituations":"SPAs or islands where cleanup unmounts roots on route change but cached component instances still call render; effects that run after unmount (missing cleanup in useEffect-equivalent) triggering a state-driven render; HMR or strict-mode double-mounting dropping references.","solutions":["Stop calling render() after unmounting; null out references to the root when you unmount it","Re-create the root with createRoot(container) before rendering again","In component lifecycles, ensure any scheduled render is cancelled in cleanup (guard with a mounted flag)"],"exampleFix":"// before\nconst root = createRoot(container)\nunmountFn = () => container.remove()\n// later, after container removed:\nroot.render(<App />) // throws\n// after\nconst root = createRoot(container)\nunmountFn = () => {\n  root.unmount()\n  rootRef.current = null\n}\nif (rootRef.current) rootRef.current.render(<App />)","handlingStrategy":"validation","validationCode":"let root: Root | null = createRoot(container)\nconst safeRender = (node: unknown) => { if (root) root.render(node) }","typeGuard":"const isLiveRoot = (r: { render: unknown } | null): boolean => r !== null","tryCatchPattern":"try {\n  root.render(<App />)\n} catch (e) {\n  if (e instanceof Error && /unmounted root/.test(e.message)) {\n    root = createRoot(container)\n    root.render(<App />)\n  }\n}","preventionTips":["Null out root references when unmounting","Cancel pending render callbacks in cleanup","Never cache roots across route/page lifecycles"],"tags":["hono","jsx","dom","lifecycle","unmounted-root"],"backgroundTag":"render-after-unmount","analyzedSha":"e2740d5a1bd0b4254e517e3af8b60789284bc7bd","analyzedAt":"2026-08-28T10:18:08.750Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}