An editable table is an essential component in many applications in the real world. While plenty of excellent React libraries are feature-rich and easy to use for editing tables, most are paid services or need to be customized to fit business requirements.
In this tutorial, weβll go through all the steps to create a dynamic editable table using TanStack Table, a Headless UI library for building robust tables and data grids in React. Since itβs just a utility, we have complete control over the UI structure and editable elements. We will be covering some of the basics, but having some basic knowledge is recommended.
The aim is to design a flexible enough component where we can provide the data source with the columns schema, and the table can adapt accordingly.
Letβs take a simplified example; if we have the data and column input below, the editable table will populate input text fields for all cells.
const data = [
{
id: 1111,
name: "Bahar Constantia",
dateOfBirth: "1984-01-04",
major: "Computer Science",
},
{
id: 2222,
name: "Harold Nona",
dateOfBirth: "1961-05-10",
major: "Communications",
},
{
id: 3333,
name: "Raginolf Arnulf",
dateOfBirth: "1991-10-12",
major: "Business",
},
{
id: 4444,
name: "Marvyn Wendi",
dateOfBirth: "1978-09-24",
major: "Psychology",
},
];
const columns = [
{
header: "Student Id",
type: "text"
},
{
header: "Full Name",
type: "text"
},
{
header: "Date Of Birth",
type: "text"
},
{
header: "Major",
type: "text"
}
];
On the other hand, if some column types change from βtextβ to βdateβ or βnumberβ, the table will respond dynamically and populate a date or number input field instead of a text field.
const columns = [
{
header: "Student Id",
type: "number"
},
{
header: "Full Name",
type: "text"
},
{
header: "Date Of Birth",
type: "date"
},
{
header: "Major",
type: "select"
}
];
The above demo is how we want the table to look and behave at the end of the tutorial. So without further ado, letβs dive into the implementationπ
Creating a basic table
To get started with TanStack Table, we have to create a model type, default data, and a column definition, which is essential to creating a basic table and, eventually, dynamic editable cells. Please refer to the official documentation for a more detailed understanding.
type Student = {
id: number;
name: string;
dateOfBirth: string;
major: string;
};
const defaultData: Student[] = [
{
id: 1111,
name: "Bahar Constantia",
dateOfBirth: "1984-01-04",
major: "Computer Science",
},
{
id: 2222,
name: "Harold Nona",
dateOfBirth: "1961-05-10",
major: "Communications",
},
{
id: 3333,
name: "Raginolf Arnulf",
dateOfBirth: "1991-10-12",
major: "Business",
},
{
id: 4444,
name: "Marvyn Wendi",
dateOfBirth: "1978-09-24",
major: "Psychology",
},
];
const columnHelper = createColumnHelper<Student>();
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
}),
columnHelper.accessor("name", {
header: "Full Name",
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
}),
columnHelper.accessor("major", {
header: "Major",
}),
];
columns
can be a regular object, but using createColumnHelper
provides a utility for creating different column definition types.
Letβs add the rest of the code to get the basic TanStack table running.
import { useState } from "react";
import "./table.css";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
type Student = {
id: number;
name: string;
dateOfBirth: string;
major: string;
};
const defaultData: Student[] = [
{
id: 1111,
name: "Bahar Constantia",
dateOfBirth: "1984-01-04",
major: "Business",
},
{
id: 2222,
name: "Harold Nona",
dateOfBirth: "1961-05-10",
major: "Communications",
},
{
id: 3333,
name: "Raginolf Arnulf",
dateOfBirth: "1991-10-12",
major: "Business",
},
{
id: 4444,
name: "Marvyn Wendi",
dateOfBirth: "1978-09-24",
major: "Business",
},
];
const columnHelper = createColumnHelper<Student>();
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
}),
columnHelper.accessor("name", {
header: "Full Name",
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
}),
columnHelper.accessor("major", {
header: "Major",
}),
];
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
return (
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
);
};
In addition, we added some styling to the table - margins, alignments, and borders.
table {
font-family: sans-serif;
border-collapse: collapse;
border: 1px solid #ccc;
margin: 25px;
background-color: #fff;
}
tr {
border-bottom: 1px solid #ccc;
}
th, td {
text-align: left;
padding: 8px 10px;
}
Adding editable cells
Weβll start with the most straightforward approach by replacing cells with input text fields, and weβll make sure to structure the code in a way that will scale as we progress to the other sections.
Letβs create a new Editable Cell component with a state and an input field.
const EditableCell = () => {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
The column definition provides a cell
prop to render any element we need instead of the default text value. This way, we can add the new EditableCell
component in all columns.
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
}),
];
When the cell
prop renders the component, it will provide all the information we need to access the table and update the value. But before going there, we need to create a new function in the main EditableTable
component to update the data based on the modified row and column.
The function will have three parameters - row index, column id, and value.
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
updateData: (rowIndex, columnId, value) => {
setData((old) =>
old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
},
},
});
return (...);
Notice that we added the function in a meta
object, which is part of the useReactTable
options and can be accessed anywhere the table is available via table.options.meta
.
Now that we have all the functions and props we need, letβs complete the EditableCell
component to trigger the update function.
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
table.options.meta?.updateData(row.index, column.id, value);
};
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
);
};
We are using the onBlur
to trigger the updateData
function, and as we can see, itβs now available in table.options.meta
. In addition, weβre passing a default value to the input field using getValue
, which is available for us from the props.
Now, letβs put everything together and look at what we have so far.
import { useState, useEffect } from "react";
import "./table.css";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
type Student = {
id: number;
name: string;
dateOfBirth: string;
major: string;
};
const defaultData: Student[] = [
{
id: 1111,
name: "Bahar Constantia",
dateOfBirth: "1984-01-04",
major: "Business",
},
{
id: 2222,
name: "Harold Nona",
dateOfBirth: "1961-05-10",
major: "Communications",
},
{
id: 3333,
name: "Raginolf Arnulf",
dateOfBirth: "1991-10-12",
major: "Business",
},
{
id: 4444,
name: "Marvyn Wendi",
dateOfBirth: "1978-09-24",
major: "Business",
},
];
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
table.options.meta?.updateData(row.index, column.id, value);
};
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
);
};
const columnHelper = createColumnHelper<Student>();
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
}),
];
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
updateData: (rowIndex, columnId, value) => {
setData((old) =>
old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
},
},
});
return (
<>
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<pre>{JSON.stringify(data, null, "\t")}</pre>
</>
);
};
The object is printed out at the end to validate that the input updates are working as expected.
Supporting dynamic types
So far, weβve created an editable table that only supports text fields. As mentioned in the introduction, the goal is to be able to define different column types and render the appropriate elements dynamically.
Letβs start by adding a new type
prop to the columns
object.
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
meta: {
type: "number",
},
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
meta: {
type: "text",
},
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
meta: {
type: "date",
},
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
meta: {
type: "text",
},
}),
];
Similar to the table, the columns provide a meta
object to add whatever weβd like to it. All we need to do is get the type from the meta
object and pass it to the input field in the EditableCell
component.
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
table.options.meta?.updateData(row.index, column.id, value);
};
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
type={column.columnDef.meta?.type || "text"}
/>
);
};
Voila! That was fast!
What if we want a more complex data type, like a select element? Well, for that, we need to do a bit more work π
Here are the steps we need to follow:
- Change the βMajorβ col type to βselectβ and provide all the select options within the column
meta
. Remember, we want to make this as dynamic as possible to support different data models. - Introduce a new
select
element in theEditableCell
component, which will populate all options from the columnmeta
object.
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const columnMeta = column.columnDef.meta;
const tableMeta = table.options.meta;
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
tableMeta?.updateData(row.index, column.id, value);
};
const onSelectChange = (e) => {
setValue(e.target.value);
tableMeta?.updateData(row.index, column.id, e.target.value);
};
return columnMeta?.type === "select" ? (
<select onChange={onSelectChange} value={initialValue}>
{columnMeta?.options?.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
) : (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
type={columnMeta?.type || "text"}
/>
);
};
const columnHelper = createColumnHelper<Student>();
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
meta: {
type: "number",
},
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
meta: {
type: "text",
},
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
meta: {
type: "date",
},
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
meta: {
type: "select",
options: [
{ value: "Computer Science", label: "Computer Science" },
{ value: "Communications", label: "Communications" },
{ value: "Business", label: "Business" },
{ value: "Psychology", label: "Psychology" },
],
},
}),
];
As we can see in the code and demo above, the βnumberβ, βdateβ and βselectβ along with the options, are all correctly showing. For the select
element, we defined a new onSelectChange
event to trigger the update function. Additionally, we did some code cleanup to make accessing column
and table
less verbose.
Editable rows
We often want the table to be read-only and switch to an edit mode on demand. The reasons could be performance enhancement or want to submit data to the server, so avoid triggering an API call on every field.
A common practice is to add an action button in a new column that will toggle the row between edit and non-edit mode. It will also contain a cancel action in case the user wants to abort changes.
Letβs start with creating a selectedRow
state, a key/value object that indicates which rows are in edit mode. Weβll also add the getter and setter to the table meta
so we can access it from other components.
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const [selectedRow, setSelectedRow] = useState({});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
selectedRow,
setSelectedRow,
...
},
});
return (...);
};
Then, we can create an EditAction
component with βeditβ, βcancelβ, and βdoneβ buttons. βcancelβ and βdoneβ will only show if the selectedRow
contains the row id set to true
.
const EditAction = ({ row, table }) => {
const meta = table.options.meta;
const setSelectedRow = () => {
meta?.setSelectedRow((old) => ({
...old,
[row.id]: !old[row.id],
}));
};
return meta?.selectedRow[row.id] ? (
<>
<button>X</button> <button onClick={setSelectedRow}>β</button>
</>
) : (
<button onClick={setSelectedRow}>β</button>
);
};
We also defined an event, a simple true/false toggle for the current row id using the setter function we just passed in the meta
options.
Now, the component has to be placed in a new column. TanStack Table provides a display
column option, which means it wonβt be part of the data model as its only purpose is to manage the editable state.
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
meta: {
type: "number",
},
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
meta: {
type: "text",
},
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
meta: {
type: "date",
},
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
meta: {
type: "select",
options: [
{ value: "Computer Science", label: "Computer Science" },
{ value: "Communications", label: "Communications" },
{ value: "Business", label: "Business" },
{ value: "Psychology", label: "Psychology" },
],
},
}),
columnHelper.display({
id: "edit",
cell: EditAction,
}),
];
Given what we have so far, the EditableCell
component can easily toggle between edit and non-edit modes by checking the selectedRow
in the table meta
object.
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const columnMeta = column.columnDef.meta;
const tableMeta = table.options.meta;
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
tableMeta?.updateData(row.index, column.id, value);
};
const onSelectChange = (e) => {
setValue(e.target.value);
tableMeta?.updateData(row.index, column.id, e.target.value);
};
if (tableMeta?.selectedRow[row.id]) {
return columnMeta?.type === "select" ? (
<select onChange={onSelectChange} value={initialValue}>
{columnMeta?.options?.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
) : (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
type={columnMeta?.type || "text"}
/>
);
}
return <span>{value}</span>;
};
Last but not least, implementing a cancel action. While there are multiple ways to achieve that, depending on the business rules and the data flow, weβll go with a simple approach for this tutorial.
Letβs create an originalData
, a copy of the data
array. If the user chooses to save the row, it will update the originalData
with the newly updated row. Otherwise, it will revert to the original row.
In the Edtiable Table component, we can add a new state with a revert function.
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const [originalData, setOriginalData] = useState(() => [...defaultData]);
const [selectedRow, setSelectedRow] = useState({});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
selectedRow,
setSelectedRow,
revertData: (rowIndex, revert) => {
if (revert) {
setData((old) =>
old.map((row, index) =>
index === rowIndex ? originalData[rowIndex] : row
)
);
} else {
setOriginalData((old) =>
old.map((row, index) => (index === rowIndex ? data[rowIndex] : row))
);
}
},
updateData: ...
});
return (...);
};
And in the EditableAction
component, we can call the function when the action button is triggered.
const EditAction = ({ row, table }) => {
const meta = table.options.meta;
const setSelectedRow = (e) => {
meta?.setSelectedRow((old) => ({
...old,
[row.id]: !old[row.id],
}));
meta?.revertData(row.index, e.target.name === "cancel");
};
return meta?.selectedRow[row.id] ? (
<>
<button onClick={setSelectedRow} name="cancel">
X
</button>{" "}
<button onClick={setSelectedRow}>β</button>
</>
) : (
<button onClick={setSelectedRow}>β</button>
);
};
Here is the code and demo of what we have so far.
import { useState, useEffect } from "react";
import "./table.css";
import {
createColumnHelper,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
type Student = {
id: number;
name: string;
dateOfBirth: string;
major: string;
};
const defaultData: Student[] = [
{
id: 1111,
name: "Bahar Constantia",
dateOfBirth: "1984-01-04",
major: "Computer Science",
},
{
id: 2222,
name: "Harold Nona",
dateOfBirth: "1961-05-10",
major: "Communications",
},
{
id: 3333,
name: "Raginolf Arnulf",
dateOfBirth: "1991-10-12",
major: "Business",
},
{
id: 4444,
name: "Marvyn Wendi",
dateOfBirth: "1978-09-24",
major: "Psychology",
},
];
const EditableCell = ({ getValue, row, column, table }) => {
const initialValue = getValue();
const columnMeta = column.columnDef.meta;
const tableMeta = table.options.meta;
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
const onBlur = () => {
tableMeta?.updateData(row.index, column.id, value);
};
const onSelectChange = (e) => {
setValue(e.target.value);
tableMeta?.updateData(row.index, column.id, e.target.value);
};
if (tableMeta?.selectedRow[row.id]) {
return columnMeta?.type === "select" ? (
<select onChange={onSelectChange} value={initialValue}>
{columnMeta?.options?.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
) : (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
type={columnMeta?.type || "text"}
/>
);
}
return <span>{value}</span>;
};
const EditAction = ({ row, table }) => {
const meta = table.options.meta;
const setSelectedRow = (e) => {
meta?.setSelectedRow((old) => ({
...old,
[row.id]: !old[row.id],
}));
meta?.revertData(row.index, e.target.name === "cancel");
};
return meta?.selectedRow[row.id] ? (
<>
<button onClick={setSelectedRow} name="cancel">
X
</button>{" "}
<button onClick={setSelectedRow}>β</button>
</>
) : (
<button onClick={setSelectedRow}>β</button>
);
};
const columnHelper = createColumnHelper<Student>();
const columns = [
columnHelper.accessor("id", {
header: "Student ID",
cell: EditableCell,
meta: {
type: "number",
},
}),
columnHelper.accessor("name", {
header: "Full Name",
cell: EditableCell,
meta: {
type: "text",
},
}),
columnHelper.accessor("dateOfBirth", {
header: "Date Of Birth",
cell: EditableCell,
meta: {
type: "date",
},
}),
columnHelper.accessor("major", {
header: "Major",
cell: EditableCell,
meta: {
type: "select",
options: [
{ value: "Computer Science", label: "Computer Science" },
{ value: "Communications", label: "Communications" },
{ value: "Business", label: "Business" },
{ value: "Psychology", label: "Psychology" },
],
},
}),
columnHelper.display({
id: "edit",
cell: EditAction,
}),
];
export const EdtiableTable = () => {
const [data, setData] = useState(() => [...defaultData]);
const [originalData, setOriginalData] = useState(() => [...defaultData]);
const [selectedRow, setSelectedRow] = useState({});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
meta: {
selectedRow,
setSelectedRow,
revertData: (rowIndex, revert) => {
if (revert) {
setData((old) =>
old.map((row, index) =>
index === rowIndex ? originalData[rowIndex] : row
)
);
} else {
setOriginalData((old) =>
old.map((row, index) => (index === rowIndex ? data[rowIndex] : row))
);
}
},
updateData: (rowIndex, columnId, value) => {
setData((old) =>
old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value,
};
}
return row;
})
);
},
},
});
return (
<>
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<pre>{JSON.stringify(data, null, "\t")}</pre>
</>
);
};
Table styling
To finish up this tutorial, letβs treat ourselves a bit and style the table with a simple yet effective CSS π
table {
border-collapse: collapse;
margin: 25px 0;
font-size: 14px;
font-family: sans-serif;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
width: 720px;
}
table thead tr {
background-color: #4bbd7f;
color: #ffffff;
text-align: left;
}
table th,
table td {
padding: 10px 15px;
}
table tbody tr {
border-bottom: 1px solid #dddddd;
}
table tbody tr:nth-of-type(even) {
background-color: #f3f3f3;
}
table tbody tr:last-of-type {
border-bottom: 2px solid #4bbd7f;
}
table tbody tr.active-row {
font-weight: bold;
color: #4bbd7f;
}
table td:first-child input {
width: 50px;
}
table td:nth-child(2) input {
width: 120px;
}
table button {
border-radius: 50px;
height: 26px;
width: 26px;
border: 1px solid #ccc;
color: #ffb918;
}
table button[name="cancel"] {
color: #7b7b7b;
}
table button[name="done"] {
color: #4bbd7f;
}
table input, select {
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px;
}
Nothing out of the ordinary here, some table shadow with row colors, borders, margin, and width!
Access complete code
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 access the code on StackBlitz below.
Summary
In this tutorial, we learned how to utilize TanckStack to build a dynamic React editable table that supports custom column schema and row editing, saving, and canceling actions.
The solution we explored together is one of several different approaches that we can take. We also didnβt consider any performance impact. So itβs highly recommended, based on the business requirement, to conduct performance tests and asses if the code might require design pattern adjustments such as using React memo, signals, or a state management to enhance performance level.
I hope you enjoyed learning from this tutorial and if you have any questions, please leave a comment below.
Bye for now!