honojs/hono · error · Error
Cannot update an unmounted root
Error message
Cannot update an unmounted root
What it means
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.
Source
Thrown at src/jsx/dom/client.ts:41
export const createRoot = (
element: HTMLElement | DocumentFragment,
options: RootOptions = {}
): Root => {
let setJsxNode:
| undefined // initial state
| ((jsxNode: unknown) => void) // rendered
| null = // unmounted
undefined
if (Object.keys(options).length > 0) {
console.warn('createRoot options are not supported yet')
}
return {
render(jsxNode: unknown) {
if (setJsxNode === null) {
// unmounted
throw new Error('Cannot update an unmounted root')
}
if (setJsxNode) {
// rendered
setJsxNode(jsxNode)
} else {
renderNode(
buildNode({
tag: () => {
const [_jsxNode, _setJsxNode] = useState(jsxNode)
setJsxNode = _setJsxNode
return _jsxNode
},
props: {},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any) as NodeObject,
element
)
}View on GitHub (pinned to e2740d5a1b)
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)
Example fix
// before
const root = createRoot(container)
unmountFn = () => container.remove()
// later, after container removed:
root.render(<App />) // throws
// after
const root = createRoot(container)
unmountFn = () => {
root.unmount()
rootRef.current = null
}
if (rootRef.current) rootRef.current.render(<App />) Defensive patterns
Strategy: validation
Validate before calling
let root: Root | null = createRoot(container)
const safeRender = (node: unknown) => { if (root) root.render(node) } Type guard
const isLiveRoot = (r: { render: unknown } | null): boolean => r !== null Try / catch
try {
root.render(<App />)
} catch (e) {
if (e instanceof Error && /unmounted root/.test(e.message)) {
root = createRoot(container)
root.render(<App />)
}
} Prevention
- Null out root references when unmounting
- Cancel pending render callbacks in cleanup
- Never cache roots across route/page lifecycles
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- style sheet not found
- Event handler for "${key}" is not a function
- Invalid JSX tag name: ${tag}
- Can only set one of `children` or `props.dangerouslySetInner
- Invalid prop '${key}' of type 'function' supplied to '${tag}
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/af55f2f3572cf4c3.
Report an issue: GitHub.