Data Table From Scratch. Part 9: Delete Rows

This is a single article from a series about creating of an advanced Data table component using React, TanStack Table 8, Tailwind CSS and Headless UI. In this chapter we focus on table editing capabilities

Welcome to the Data table workshop. In the …


This content originally appeared on DEV Community and was authored by Dima Vyshniakov

This is a single article from a series about creating of an advanced Data table component using React, TanStack Table 8, Tailwind CSS and Headless UI. In this chapter we focus on table editing capabilities

Welcome to the Data table workshop. In the previous exercise, we implemented row selection. Now, we'll apply that functionality to build one of the most common table interactions: row deletion.

We will implement the following workflow:

  • User selects certain rows (e.g., result of filtering).
  • User clicks the delete button.
  • User confirms the choice in a dialog.

Deletion demo

Implement row deletion logic

To make the table's data editable, we must first stop treating it as a static prop and manage it as internal state. The best way to encapsulate this logic is by creating a custom React hook at src/DataTable/features/useTableData.ts.

The hook receives the four props:

  • tableDataProp: Row[]: all table rows;
  • rowSelection: RowSelectionState: informs the hook about current selection;
  • clearSelection: () => void: helper to reset the selection UI;
  • onEdit: (editState: EditState) => void: callback to inform which rows are edited.

Inside the useTableData hook, we first clone the incoming tableDataProp into local state. We use an arrow function inside useState to ensure this cloning operation runs only once on the initial render, not on every re-render. An accompanying useEffect hook keeps our local state synchronized if the tableDataProp changes from an external source, like a new data fetch.

const [tableData, setTableData] = useState(() => [...tableDataProp]);

useEffect(() => {
  setTableData(() => [...tableDataProp]);
}, [tableDataProp]);

deleteRows function

The core of our new hook is the deleteRows function. It orchestrates the entire deletion process in a few clear steps.

First, it converts the rowSelection object (e.g., { '2': true, '5': true }) into an array of numeric row indices. For efficient lookup, this array is then converted into a Set. Using this Set, we filter the tableData array, keeping only the rows whose indices are not in the rowsToDelete set. This new, filtered array becomes our nextTableData.

After updating the local state with setTableData, we build an EditState map to inform the parent component which rows were removed. Finally, we call clearSelection to reset the UI, unchecking the checkboxes for the now-deleted rows.

const deleteRows = useCallback(() => {
  const normalizedRows = Object.keys(rowSelection).map((rowIndex) =>
    Number(rowIndex),
  );

  const rowsToDelete = new Set(normalizedRows);

  const nextTableData = tableData.filter((_, i) => !rowsToDelete.has(i));

  setTableData(nextTableData);

  const editState: EditState = Object.fromEntries(
    normalizedRows.map(rowIndex => [rowIndex, false])
  );

  onEdit(editState);

  clearSelection();
}, [clearSelection, onEdit, rowSelection, tableData]);

Integrate hook to DataTable component

With our hook ready, let's integrate it into the main src/DataTable/DataTable.tsx component. We'll start by adding a new optional prop, onTableEdit, to allow parent components to react to data changes.

type Props = {
  //...
  /**
   * Callback to capture table data changes
   * @see RowSelectionState
   */
  onTableEdit?: (editState: EditState) => void;
};

Next, we'll call our new hook inside the DataTable component. Notice the clean separation of concerns: useRowSelection manages which rows are selected, while useTableData handles the data manipulation. By passing handleClearSelection as a callback, we allow the data hook to control the selection UI without being directly coupled to it. This keeps our logic pure and more testable.

Finally, we pass the stateful tableData from our hook directly into the useReactTable instance instead of the original prop.

const { rowSelection, handleRowSelection, handleClearSelection } =
  useRowSelection({
    rowSelectionProp,
    onRowSelect,
  });

const { tableData, deleteRows } = useTableData({
  tableDataProp,
  rowSelection,
  clearSelection: handleClearSelection,
  onEdit: onTableEdit
});

const table = useReactTable({
  //...
  data: tableData,
})

Create deletion UI

Now for the user-facing part. To prevent accidental deletions, we'll create a confirmation dialog at src/DataTable/dialogs/DeleteDialog.tsx.

Deletion dialog

Outside a standard dialog properties DeleteDialog receives rowsAmount to display how many rows are selected for deletion, and onDelete: callback that will trigger deleteRows function.

Inside the component, the delete click handler invokes both onDelete and onClose callbacks.

export const DeleteDialog: FC<Props> = ({
  locale,
  rowsAmount,
  isOpen,
  onClose,
  onDelete,
}) => {
  const handleDelete = useCallback(() => {
    onDelete();
    onClose();
  }, [onClose, onDelete]);
  return (
    <TableDialog title="Confirm deletion" open={isOpen} onClose={onClose}>
      <div className="text-white/80">
        Do you want to delete{' '}
        <span className="font-semibold tabular-nums">
          {formatRowsAmount(rowsAmount, locale)}
        </span>{' '}
        row(s)?
      </div>
      <div className="mt-6 flex justify-evenly gap-3">
        <Button
          className="min-w-32"
          onClick={onClose}
          title="Cancel"
          icon="hand-palm"
        />
        <Button
          className="min-w-32"
          onClick={handleDelete}
          title="Delete"
          icon="trash"
        />
      </div>
    </TableDialog>
  );
};

Connect DeleteDialog with DataTable

DeleteDialog is built to consume deleteRows and handleClearSelection functions from the hooks we updated.

<DeleteDialog
  onDelete={deleteRows}
  onClose={closeDeleteDialog}
  rowsAmount={selectedRows.length}
/>

Demo

Here is a working demo of this exercise.

To be continued.


This content originally appeared on DEV Community and was authored by Dima Vyshniakov


Print Share Comment Cite Upload Translate Updates
APA

Dima Vyshniakov | Sciencx (2025-10-05T19:15:04+00:00) Data Table From Scratch. Part 9: Delete Rows. Retrieved from https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/

MLA
" » Data Table From Scratch. Part 9: Delete Rows." Dima Vyshniakov | Sciencx - Sunday October 5, 2025, https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/
HARVARD
Dima Vyshniakov | Sciencx Sunday October 5, 2025 » Data Table From Scratch. Part 9: Delete Rows., viewed ,<https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/>
VANCOUVER
Dima Vyshniakov | Sciencx - » Data Table From Scratch. Part 9: Delete Rows. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/
CHICAGO
" » Data Table From Scratch. Part 9: Delete Rows." Dima Vyshniakov | Sciencx - Accessed . https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/
IEEE
" » Data Table From Scratch. Part 9: Delete Rows." Dima Vyshniakov | Sciencx [Online]. Available: https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/. [Accessed: ]
rf:citation
» Data Table From Scratch. Part 9: Delete Rows | Dima Vyshniakov | Sciencx | https://www.scien.cx/2025/10/05/data-table-from-scratch-part-9-delete-rows/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.