{"record":{"id":"eecb599282671bc1","repo":"facebook/lexical","slug":"table-row-target-index-out-of-range","errorCode":null,"errorMessage":"Table row target index out of range","messagePattern":"Table row target index out of range","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/lexical-table/src/LexicalTableUtils.ts","lineNumber":195,"sourceCode":"  const targetRowNode = tableRows[indexToDelete];\n  targetRowNode.remove();\n  return tableNode;\n}\n\n/**\n * @deprecated This function does not support merged cells. Use {@link $insertTableRowAtSelection} or {@link $insertTableRowAtNode} instead.\n */\nexport function $insertTableRow(\n  tableNode: TableNode,\n  targetIndex: number,\n  shouldInsertAfter = true,\n  rowCount: number,\n  table: TableDOMTable,\n): TableNode {\n  const tableRows = tableNode.getChildren();\n\n  if (targetIndex >= tableRows.length || targetIndex < 0) {\n    throw new Error('Table row target index out of range');\n  }\n\n  const targetRowNode = tableRows[targetIndex];\n\n  if ($isTableRowNode(targetRowNode)) {\n    for (let r = 0; r < rowCount; r++) {\n      const tableRowCells = targetRowNode.getChildren();\n      const tableColumnCount = tableRowCells.length;\n      const newTableRowNode = $createTableRowNode();\n\n      for (let c = 0; c < tableColumnCount; c++) {\n        const tableCellFromTargetRow = tableRowCells[c];\n\n        invariant(\n          $isTableCellNode(tableCellFromTargetRow),\n          'Expected table cell',\n        );\n","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/facebook/lexical/blob/76a22dcba981b46119fcec45f935270707e26a57/packages/lexical-table/src/LexicalTableUtils.ts#L177-L213","documentation":"$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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Clamp or validate targetIndex: keep it in [0, tableNode.getChildrenSize() - 1], flipping shouldInsertAfter when inserting past the end.","Derive targetIndex inside the update from the live TableNode children, not from cached DOM row indices.","For appending rows after the last row, pass targetIndex = lastRowIndex with shouldInsertAfter = true, not rowCount."],"exampleFix":"// before\n$insertTableRow(tableNode, tableNode.getChildrenSize(), true, 1, dom);\n// after\nconst last = tableNode.getChildrenSize() - 1;\n$insertTableRow(tableNode, last, true, 1, dom);","handlingStrategy":"validation","validationCode":"const rowCount = tableNode.getChildrenSize();\nconst idx = Math.min(Math.max(targetIndex, 0), rowCount - 1);\nconst after = targetIndex >= rowCount ? true : shouldInsertAfter;\n$insertTableRow(tableNode, idx, after, n, dom);","typeGuard":"function isRowTargetValid(tableNode: TableNode, idx: number): boolean {\n  return idx >= 0 && idx < tableNode.getChildrenSize();\n}","tryCatchPattern":"try {\n  $insertTableRow(tableNode, idx, true, 1, dom);\n} catch (e) {\n  if (e.message === 'Table row target index out of range') {\n    $insertTableRow(tableNode, tableNode.getChildrenSize() - 1, true, 1, dom);\n  } else throw e;\n}","preventionTips":["Append below last row using lastIndex + shouldInsertAfter, not rowCount as index","Derive indices from live Lexical children, not DOM row positions","Clamp user-provided indices before calling insert APIs"],"tags":["lexical-table","index-out-of-range"],"backgroundTag":"index-out-of-range","analyzedSha":"76a22dcba981b46119fcec45f935270707e26a57","analyzedAt":"2026-08-31T21:45:54.792Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}