BookStackApp/BookStack · error · Error

${error.toString()} Parent: ${dom.tagName}, new child: {tag:

Error message

${error.toString()} Parent: ${dom.tagName}, new child: {tag: ${replacementDOM.tagName} key: ${nextFrstChildKey}}, old child: {tag: ${lastDOM.tagName}, key: ${prevFirstChildKey}}.

What it means

Lexical's development-mode reconciler ($reconcileChildren) throws this internal invariant when it must destroy the first child DOM node during reconciliation and detects a tag/key mismatch between the existing DOM child and the replacement node — i.e. Lexical's node state and the rendered DOM have diverged. The message embeds the parent tag, new child tag/key, and old child tag/key to help report the invariant violation upstream.

Source

Thrown at resources/js/wysiwyg/lexical/core/LexicalReconciler.ts:378

    const prevFirstChildKey = prevElement.__first as NodeKey;
    const nextFrstChildKey = nextElement.__first as NodeKey;
    if (prevFirstChildKey === nextFrstChildKey) {
      $reconcileNode(prevFirstChildKey, dom);
    } else {
      const lastDOM = getPrevElementByKeyOrThrow(prevFirstChildKey);
      const replacementDOM = $createNode(nextFrstChildKey, null, null);
      try {
        dom.replaceChild(replacementDOM, lastDOM);
      } catch (error) {
        if (typeof error === 'object' && error != null) {
          const msg = `${error.toString()} Parent: ${
            dom.tagName
          }, new child: {tag: ${
            replacementDOM.tagName
          } key: ${nextFrstChildKey}}, old child: {tag: ${
            lastDOM.tagName
          }, key: ${prevFirstChildKey}}.`;
          throw new Error(msg);
        } else {
          throw error;
        }
      }
      destroyNode(prevFirstChildKey, null);
    }
    const nextChildNode = activeNextNodeMap.get(nextFrstChildKey);
    if ($isTextNode(nextChildNode)) {
      if (subTreeTextFormat === null) {
        subTreeTextFormat = nextChildNode.getFormat();
      }
      if (subTreeTextStyle === '') {
        subTreeTextStyle = nextChildNode.getStyle();
      }
    }
  } else {
    const prevChildren = createChildrenArray(prevElement, activePrevNodeMap);
    const nextChildren = createChildrenArray(nextElement, activeNextNodeMap);

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Wrap all node mutations in editor.update() callbacks; never mutate nodes directly from listeners
  2. Use $applyNodeReplacement / proper node APIs when replacing nodes in custom plugins
  3. Reproduce in dev build and report to Lexical maintainers with the tag/key info from the message — this is an internal invariant
  4. Pin/upgrade Lexical and plugins to matching versions

Example fix

// before
node.replace(newNode); // outside editor.update()
// after
editor.update(() => {
  newNode.replace(node);
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure all mutations happen inside editor.update()
if (!editor._updateInProgress /* or via wrapper */) {
  // refactor: schedule inside update instead of mutating now
  editor.update(() => performMutation());
}

Type guard

function isInsideLexicalUpdate(fn: () => void): () => void {
  return () => editor.update(fn);
}

Try / catch

try {
  editor.update(() => applyNodeChanges());
} catch (e) {
  console.error('Lexical reconciliation invariant violated', e);
  // recover: reload editor state from last serialized snapshot
  editor.setEditorState(lastGoodState);
}

Prevention

When it happens

Trigger: $reconcileChildrenWithDirection encountering a corrupted reconciliation path during $commitPendingUpdates: typically after a plugin mutates nodes outside updateEditor, or a node transformer/listener mutates the tree during reconciliation.

Common situations: Third-party Wysiwyg plugins calling editor APIs outside editor.update(); concurrent mutations from event listeners; Lexical version upgrades with custom plugins relying on old behavior; programmatic node replacement without $applyNodeReplacement.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/6fecc38fa70af87f. Report an issue: GitHub.