Muhi Logo Text
AboutBlogWork With Me

[Part 2] Add and Remove Table Rows with React TanStack

A guide to adding and removing table rows with multiple row selections using React TanStack

Last updated on July 23, 2023

react
tanstack
React Add Remove Table Rows

In the previous part, we went through a detailed approach to creating an editable React table with a dynamic column schema. We also utilized TanStack Table to simplify the process. The code from the previous part is available here in Stackblitz.

In this part, we will continue building on what we’ve done previously and add the ability to remove and add table rows with multiple row selections.

Below is a short video of how we want the table to look and behave at the end of this tutorial:

As we continue building upon the same code from the previous part, it’s recommended that you go through the first tutorial to understand how the code structure works as we will not be explaining it here. You can navigate to any part using the table of contents.

Add table row

To enable adding table rows, we must add an “Add Row” button and then create the logic to insert an empty row into the data array.

Let’s start by adding the logic to insert the row in the main table component. Like the previous approach, we’ll attach the function in the meta object in the useReactTableHook. This way, we can access it anywhere through the main table object:

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      editedRows,
      setEditedRows,
      revertData: (...) => {
        ...
      },
      updateData: (...) => {
        ...
      },
      addRow: () => {
        const newRow: Student = {
          studentId: Math.floor(Math.random() * 10000),
          name: "",
          dateOfBirth: "",
          major: "",
        };
        const setFunc = (old: Student[]) => [...old, newRow];
        setData(setFunc);
        setOriginalData(setFunc);
      },
    },
  });

Note that we also updated the originalData array, as we need to keep them in sync for the cancel/save row functionality discussed in the first part.

Now, we can create a Footer Cell component with a button and event handler to consume the addRow function.

export const FooterCell = ({ table }) => {
  const meta = table.options.meta

  return (
    <div className="footer-buttons">
      <button className="add-button" onClick={meta?.addRow}>
        Add New +
      </button>
    </div>
  )
}

Lastly, we need to display the FooterCell component somewhere on the table for the user to consume. There are multiple options, but adding it to the footer’s main table can be a good place as it will feel more integrated.

import { FooterCell } from '../Table/FooterCell';

export const Table = () => {
  const [data, setData] = useState(() => [...defaultData]);
  const [originalData, setOriginalData] = useState(() => [...defaultData]);
  const [editedRows, setEditedRows] = useState({});

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      editedRows,
      setEditedRows,
      revertData: (...) => {
       ...
      },
      updateData: (...) => {
        ...
      },
      addRow: () => {
        const newRow: Student = {
          studentId: Math.floor(Math.random() * 10000),
          name: "",
          dateOfBirth: "",
          major: "",
        };
        const setFunc = (old: Student[]) => [...old, newRow];
        setData(setFunc);
        setOriginalData(setFunc);
      },
    },
  });

  return (
    <article className="table-container">
      <table>
        <thead>
          ...
        </thead>
        <tbody>
          ...
        </tbody>
        <tfoot>
          <tr>
            <th colSpan={table.getCenterLeafColumns().length} align="right">
              <FooterCell table={table} />
            </th>
          </tr>
        </tfoot>
      </table>
    </article>
  );
};

Below is a live example of the code above with some button styling:

table .footer-buttons button {
  border: none;
  background-color: transparent;
}

table .add-button {
  color: #4bbd7f;
}

Remove table row

Removing table rows will follow the same approach as the previous section. Let’s start by adding the removeRow function, a simple one-liner filter method to remove the row from the data and original data arrays.

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    meta: {
      editedRows,
      setEditedRows,
      revertData: (...) => {
        ...
      },
      updateData: (...) => {
        ...
      },
      addRow: () => {
        ...
      },
      removeRow: (rowIndex: number) => {
        const setFilterFunc = (old: Student[]) =>
          old.filter((_row: Student, index: number) => index !== rowIndex);
        setData(setFilterFunc);
        setOriginalData(setFilterFunc);
      },
    },
  });

For the “Remove” button, creating a new component wouldn’t be necessary as it will reside next to the “Edit” button in the EditCell component.

Now, we can add a removeRow function in the EditCell component and call it on the “Remove” button’s onClick event.

export const EditCell = ({ row, table }) => {
  const meta = table.options.meta;

  const setEditedRows = (...) => {
   ...
  };

  const removeRow = () => {
    meta?.removeRow(row.index);
  };

  return (
    <div className="edit-cell-container">
      {meta?.editedRows[row.id] ? (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="cancel">

          </button>{" "}
          <button onClick={setEditedRows} name="done">

          </button>
        </div>
      ) : (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="edit">

          </button>
          <button onClick={removeRow} name="remove">
            X
          </button>
        </div>
      )}
    </div>
  );
};

Note that we changed the “Cancel” icon to a minus sign (⚊) and the “Remove” icon to a cross (X) for better clarity.

Below is a live demo of the code above with some button styling and alignment:

table .edit-cell-action button[name="edit"] {
  color: #ffb918;
}

table .edit-cell-action button[name="cancel"] {
  color: #7b7b7b;
}

table .edit-cell-action button[name="done"] {
  color: #4bbd7f;
}

table .edit-cell-action button[name="remove"] {
  color: red;
  background-color: rgb(230, 208, 208);
}
table .edit-cell-action {
  display: flex;
  gap: 5px;
}

Remove multiple table rows

It’s always helpful to select and remove multiple rows at once. Luckily, row selection is available out of the box from TanStack Table. All we need to do is enable the feature and pass in the proper methods; it will manage the rest for us.

First, we’ll add enableRowSelection in useReactTable and create a new removeSelectedRows function. The function will receive an array of selected row IDs and filter them out from the data array.

const table = useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  enableRowSelection: true,
  meta: {
    editedRows,
    setEditedRows,
    revertData: (...) => {
      ...
    },
    updateData: (...) => {
      ...
    },
    addRow: () => {
      ...
    },
    removeRow: (...) => {
      ...
    },
    removeSelectedRows: (selectedRows: number[]) => {
      const setFilterFunc = (old: Student[]) =>
        old.filter((_row, index) => !selectedRows.includes(index));
      setData(setFilterFunc);
      setOriginalData(setFilterFunc);
    },
  },
});

Then, in the EditCell component, we’ll add a checkbox input with the appropriate props to capture and update the selected rows. The event handlers are coming from the row object available for us from the useReactTable hook.

export const EditCell = ({ row, table }) => {
  const meta = table.options.meta;

  const setEditedRows = (...) => {
    ...
  };

  const removeRow = () => {
    meta?.removeRow(row.index);
  };

  return (
    <div className="edit-cell-container">
      {meta?.editedRows[row.id] ? (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="cancel">

          </button>{" "}
          <button onClick={setEditedRows} name="done">

          </button>
        </div>
      ) : (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="edit">

          </button>
          <button onClick={removeRow} name="remove">
            X
          </button>
        </div>
      )}
      <input
        type="checkbox"
        checked={row.getIsSelected()}
        onChange={row.getToggleSelectedHandler()}
      />
    </div>
  );
};

Last, we’ll add a new Remove Selected button to the FooterCell component and pass in the removeSelectedRows function from the meta object. The selected rows can be retrieved from the getSelectedRowModel function that has rows property.

export const FooterCell = ({ table }) => {
  const meta = table.options.meta
  const selectedRows = table.getSelectedRowModel().rows

  const removeRows = () => {
    meta.removeSelectedRows(
      table.getSelectedRowModel().rows.map(row => row.index)
    )
    table.resetRowSelection()
  }

  return (
    <div className="footer-buttons">
      {selectedRows.length > 0 ? (
        <button className="remove-button" onClick={removeRows}>
          Remove Selected x
        </button>
      ) : null}
      <button className="add-button" onClick={meta?.addRow}>
        Add New +
      </button>
    </div>
  )
}

Additionally, we added a condition to show the Remove Selected button only if there are selected rows.

Below is the final demo, along with the rest of the styling for the Remove Selected button and check box input:

table .edit-cell-container {
  display: flex;
  justify-content: end;
  align-items: center;
  gap: 4px;
}

table input[type="checkbox"] {
  width: 16px;
  height: 16px;
}
table .remove-button {
  color: #e44747;
}

Complete code and live demo

The complete code is available in this repository. If you liked the tutorial, please star the repository, and feel free to request new features!

Alternatively, you can try the code in the live demo below:

Summary

In this tutorial, we learned how to add and remove rows from a React Table. We also learned how to add a checkbox input to select and remove multiple rows with a single click.

Bye for now 👋

If you enjoyed this post, I regularly share similar content on Twitter. Follow me @muhimasri to stay up to date, or feel free to message me on Twitter if you have any questions or comments. I'm always open to discussing the topics I write about!

Recommended Reading

Learn how to create encapsulated and reusable Fieldset component with Material UI (MUI) and React.

react
mui

Discussion

Upskill Your Frontend Development Techniques 🌟

Subscribe to stay up-to-date and receive quality front-end development tutorials straight to your inbox!

No spam, sales, or ads. Unsubscribe anytime you wish.

© 2024, Muhi Masri