ianstormtaylor/slate · error · Error

Cannot resolve a DOM node from Slate node: ${Scrubber.string

Error message

Cannot resolve a DOM node from Slate node: ${Scrubber.stringify(node)}

What it means

Thrown by DOMEditor.toDOMNode when no rendered DOM element exists for the given Slate node. The node-to-element map has no entry, meaning the node is not currently rendered (or not rendered by this editor).

Source

Thrown at packages/slate-dom/src/plugin/dom-editor.ts:568

    if (!DOMEditor.hasTarget(editor, target)) return false

    const slateNode = DOMEditor.toSlateNode(editor, target)
    return Node.isElement(slateNode) && Editor.isVoid(editor, slateNode)
  },

  setFragmentData: (editor, data, originEvent) =>
    editor.setFragmentData(data, originEvent),

  toDOMNode: (editor, node) => {
    const domNode =
      node === editor
        ? EDITOR_TO_ELEMENT.get(editor)
        : EDITOR_TO_KEY_TO_ELEMENT.get(editor)?.get(
            DOMEditor.findKey(editor, node)
          )

    if (!domNode) {
      throw new Error(
        `Cannot resolve a DOM node from Slate node: ${Scrubber.stringify(node)}`
      )
    }

    return domNode
  },

  toDOMPoint: (editor, point) => {
    const [node] = Editor.node(editor, point.path)
    const el = DOMEditor.toDOMNode(editor, node)
    let domPoint: DOMPoint | undefined

    // If we're inside a void node, force the offset to 0, otherwise the zero
    // width spacing character will result in an incorrect offset of 1
    if (Editor.void(editor, { at: point })) {
      point = { path: point.path, offset: 0 }
    }

View on GitHub (pinned to 72a37c701e)

Solutions

  1. Defer the DOM lookup to after render (requestAnimationFrame/setTimeout or useEffect)
  2. Re-resolve the node by path/key after edits instead of reusing old node objects
  3. If virtualizing, look up only nodes known to be in the viewport
  4. Check the node still exists: Node.has(editor, path) before calling

Example fix

// before
Transforms.insertNodes(editor, { text: 'hi' })
const el = DOMEditor.toDOMNode(editor, editor.children[0]) // too early
// after
requestAnimationFrame(() => {
  const el = DOMEditor.toDOMNode(editor, editor.children[0])
})
Defensive patterns

Strategy: validation

Validate before calling

// after render commit:
requestAnimationFrame(() => {
  try { const el = DOMEditor.toDOMNode(editor, node) } catch { /* not rendered */ }
})

Type guard

null

Try / catch

try { el = DOMEditor.toDOMNode(editor, node) } catch (e) { if (/Cannot resolve a DOM node/.test(e.message)) skip or re-resolve; else throw e }

Prevention

When it happens

Trigger: Calling toDOMNode for a node that was deleted, not yet rendered (before React commits), rendered virtually (e.g. outside a windowed/virtualized list that Slate renders lazily), or belonging to another editor instance.

Common situations: Calling toDOMNode immediately after inserting a node in the same tick (before render), using virtualization where off-screen nodes have no DOM, or holding a stale node after normalization replaced it.

Related errors


AI-assisted analysis of ianstormtaylor/slate@72a37c701e (2026-08-27). Data as JSON: /api/errors/b10de3964d659e73. Report an issue: GitHub.