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.

Solutions

  1. Stop calling render() after unmounting; null out references to the root when you unmount it
  2. Re-create the root with createRoot(container) before rendering again
  3. 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

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


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/af55f2f3572cf4c3. Report an issue: GitHub.

Appendix: 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)