facebook/lexical · error

Table row target index out of range

Error message

Table row target index out of range

What it means

$insertTableRow checks that targetIndex points at an existing row of the table before inserting. If targetIndex is negative or >= the current row count, the anchor row cannot be found and the function throws rather than silently appending.

Source

Thrown at packages/lexical-table/src/LexicalTableUtils.ts:195

  const targetRowNode = tableRows[indexToDelete];
  targetRowNode.remove();
  return tableNode;
}

/**
 * @deprecated This function does not support merged cells. Use {@link $insertTableRowAtSelection} or {@link $insertTableRowAtNode} instead.
 */
export function $insertTableRow(
  tableNode: TableNode,
  targetIndex: number,
  shouldInsertAfter = true,
  rowCount: number,
  table: TableDOMTable,
): TableNode {
  const tableRows = tableNode.getChildren();

  if (targetIndex >= tableRows.length || targetIndex < 0) {
    throw new Error('Table row target index out of range');
  }

  const targetRowNode = tableRows[targetIndex];

  if ($isTableRowNode(targetRowNode)) {
    for (let r = 0; r < rowCount; r++) {
      const tableRowCells = targetRowNode.getChildren();
      const tableColumnCount = tableRowCells.length;
      const newTableRowNode = $createTableRowNode();

      for (let c = 0; c < tableColumnCount; c++) {
        const tableCellFromTargetRow = tableRowCells[c];

        invariant(
          $isTableCellNode(tableCellFromTargetRow),
          'Expected table cell',
        );

View on GitHub (pinned to 76a22dcba9)

Solutions

  1. Clamp or validate targetIndex: keep it in [0, tableNode.getChildrenSize() - 1], flipping shouldInsertAfter when inserting past the end.
  2. Derive targetIndex inside the update from the live TableNode children, not from cached DOM row indices.
  3. For appending rows after the last row, pass targetIndex = lastRowIndex with shouldInsertAfter = true, not rowCount.

Example fix

// before
$insertTableRow(tableNode, tableNode.getChildrenSize(), true, 1, dom);
// after
const last = tableNode.getChildrenSize() - 1;
$insertTableRow(tableNode, last, true, 1, dom);
Defensive patterns

Strategy: validation

Validate before calling

const rowCount = tableNode.getChildrenSize();
const idx = Math.min(Math.max(targetIndex, 0), rowCount - 1);
const after = targetIndex >= rowCount ? true : shouldInsertAfter;
$insertTableRow(tableNode, idx, after, n, dom);

Type guard

function isRowTargetValid(tableNode: TableNode, idx: number): boolean {
  return idx >= 0 && idx < tableNode.getChildrenSize();
}

Try / catch

try {
  $insertTableRow(tableNode, idx, true, 1, dom);
} catch (e) {
  if (e.message === 'Table row target index out of range') {
    $insertTableRow(tableNode, tableNode.getChildrenSize() - 1, true, 1, dom);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling $insertTableRow(tableNode, targetIndex, shouldInsertAfter, rowCount, tableDOM) with an out-of-bounds targetIndex — e.g. inserting 'below' the last row using children.length as an after-index, or an index computed from stale DOM state.

Common situations: Toolbar 'insert row below' on the last row using rows.length as the index; index derived from a detached row's getIndexInRange after concurrent edits; passing 0-based vs 1-based indices inconsistently.

Related errors


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