facebook/lexical · error

Expected TableNode.

Error message

Expected TableNode.

What it means

TableObserver.$clearText() looks up the table node by the observer's stored tableNodeKey with $getNodeByKey. If the key no longer resolves to a live TableNode (undefined or a different node kind), this guard throws. It means the observer is operating against a table that has been removed from the current editor state (e.g. after an undo, transform, or selection change left stale state).

Source

Thrown at packages/lexical-table/src/LexicalTableObserver.ts:590

      : null;

    cellNodes.forEach((cellNode: TableCellNode) => {
      anchor.set(cellNode.getKey(), 0, 'element');
      focus.set(cellNode.getKey(), cellNode.getChildrenSize(), 'element');
      formatSelection.formatText(type, alignFormatWith);
    });

    $setSelection(selection);

    this.editor.dispatchCommand(SELECTION_CHANGE_COMMAND);
  }

  $clearText() {
    const {editor} = this;
    const tableNode = $getNodeByKey(this.tableNodeKey);

    if (!$isTableNode(tableNode)) {
      throw new Error('Expected TableNode.');
    }

    const selection = $getSelection();

    invariant($isTableSelection(selection), 'Expected TableSelection');

    const selectedNodes = selection.getNodes().filter($isTableCellNode);

    // Check if the entire table is selected by verifying first and last cells
    const firstRow = tableNode.getFirstChild();
    const lastRow = tableNode.getLastChild();

    const isEntireTableSelected =
      selectedNodes.length > 0 &&
      firstRow !== null &&
      lastRow !== null &&
      $isTableRowNode(firstRow) &&
      $isTableRowNode(lastRow) &&

View on GitHub (pinned to 76a22dcba9)

Solutions

  1. Check the table still exists before dispatching the clear: editor.read(() => $getNodeByKey(key) !== undefined) or re-derive the TableNode from the current selection.
  2. Do not hold TableObserver/tableNodeKey references across update boundaries; obtain the table fresh from the current selection.
  3. If the table was legitimately deleted, skip the clear instead of treating it as an error.
  4. In collaboration setups, wrap remote-sync-driven operations so removals received from Yjs invalidate pending local table operations.

Example fix

// before
observer.$clearText();
// after
editor.update(() => {
  const tableNode = $getNodeByKey(observer.tableNodeKey);
  if ($isTableNode(tableNode)) {
    observer.$clearText();
  }
});
Defensive patterns

Strategy: validation

Validate before calling

const stillValid = editor.read(() => $isTableNode($getNodeByKey(key)));

Type guard

function isLiveTable(key: NodeKey, editor: LexicalEditor): boolean {
  return editor.read(() => $isTableNode($getNodeByKey(key)));
}

Try / catch

try {
  editor.update(() => $clearTable());
} catch (e) {
  if (e instanceof Error && e.message === 'Expected TableNode.') return; // table already gone
  throw e;
}

Prevention

When it happens

Trigger: Calling $clearTable at a time when the TableNode keyed by this.tableNodeKey has been removed or replaced: the node was deleted in the pending update, the key became stale after undo/revert, or a node transform moved/replaced the table while a clear operation was in flight.

Common situations: Dispatching CLEAR_TABLE_COMMAND after deleting the table in the same update; collaborating editors where the table was removed remotely; custom transforms that recreate TableNodes (new keys) while the observer still holds the old key.

Related errors


AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31). Data as JSON: /api/errors/30ba562c0014fe42. Report an issue: GitHub.