Basic React Table
Create an instance of a react-table.
const BasicTable = () => {
......
...
const table = useReactTable({ data, columns })
return (
)
}
data is the data the table contains. Columns need to be defined based on what data the table is going to contain. The following is how you define the columns.
const columns = [
{
header: "ID",
accessorKey: "id",
footer: "ID",
},
{
header: "First Name",
accessorKey: "first_name",
footer: "First Name",
},
{
header: "Last Name",
accessorKey: "last_name",
footer: "Last Name",
},
......
...
]
what each property does:
header: This defines the column's title that appears at the top of the table. It's what users see as the column name.
accessorKey: This is the key used to extract data from each row's object. It tells the table which property from your dataset should be displayed in this column. So, accessorKey should match the exact property name in your data objects.
footer: Similar to header, but it defines the text that appears at the bottom of the column as a footer.
Render table in jsx:
{table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => (
{flexRender(header.column.columnDef.header, header.getContext())}
))}
))}
...
Why Are There headerGroups? Normally, when you define simple columns, there’s only one header row. However, if you group columns under a parent column, React Table creates multiple header rows.
What Does header.getContext() Do Here? header.getContext() creates an object that contains useful information about the column and table state. This context object is then passed into flexRender(), allowing it to properly render the header.
Why is header.getContext() Needed?
In react-table, header.column.columnDef.header can be:
1. **A string** (e.g., `"Name"` → just displayed as-is).
2. **A function** (e.g., `({ column }) => {`[`column.id`](http://column.id)`} Column` → needs context to work).
If header is a function, it requires an argument (getContext() provides this argument).
flexRender() takes this function and calls it with the necessary context.
Render Rows and Cells
...
{table.getRowModel().rows.map((row) => (
{row.getVisibleCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))}
This is how you can render the data. But data will be rendered only when getCoreRowModel is set in table instance:
// import { getCoreRowModel } from "@tanstack/react-table"
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
Set Footer
{table.getFooterGroups().map((footerGroup) => (
{footerGroup.headers.map((header) => (
{flexRender(header.column.columnDef.footer, header.getContext())}
))}
))}
Custom Cell Rendering
const columns = [
......
...
{
header: "Date of Birth",
accessorKey: "dob",
footer: "Date of Birth",
cell: (info) =>
DateTime.fromISO(info.getValue()).toLocaleString(DateTime.DATE_MED),
},
]
Basically, this is how you can customize cells.
The function parameter is the object where you can have all the information regarding the particular cell.
Accessor Function - accessorFn
accessorKey is a static value property. But instead of static value, we can have dynamic value with accessorFn
const columns = [
{
header: "ID",
accessorKey: "id",
footer: "ID",
},
{
header: "Name",
accessorFn: (row) => `${row.first_name} ${row.last_name}`, // accessor function
footer: "Name",
},
// {
// header: "First Name",
// accessorKey: "first_name",
// footer: "First Name",
// },
// {
// header: "Last Name",
// accessorKey: "last_name",
// footer: "Last Name",
// },
]
Header Groups with Child Columns
const columns = [
{
header: "ID",
accessorKey: "id",
footer: "ID",
},
{
header: "Name",
columns: [
{
header: "First",
accessorKey: "first_name",
footer: "First Name",
},
{
header: "Last",
accessorKey: "last_name",
footer: "Last Name",
},
],
},
......
...
];
Here, this creates 2 layer of header groups. This creates gibberish like the first group is going to have ID and Name. The second group is also going to have ID and then First and Last. This way it doesn’t serve our purpose. So, render them this way:
{table.getHeaderGroups().map((headerGroup) => (
{headerGroup.headers.map((header) => (
{header.isPlac
data is the data the table contains. Columns need to be defined based on what data the table is going to contain. The following is how you define the columns.
header: This defines the column's title that appears at the top of the table. It's what users see as the column name.
accessorKey: This is the key used to extract data from each row's object. It tells the table which property from your dataset should be displayed in this column. So, accessorKey should match the exact property name in your data objects.
footer: Similar to header, but it defines the text that appears at the bottom of the column as a footer.
Why Are ThereheaderGroups? Normally, when you define simple columns, there’s only one header row. However, if you group columns under a parent column, React Table creates multiple header rows.
What Doesheader.getContext() Do Here? header.getContext() creates an object that contains useful information about the column and table state. This context object is then passed into flexRender(), allowing it to properly render the header.
Why isheader.getContext() Needed?
In react-table, header.column.columnDef.header can be:
constcolumns=[.........{header:"Date of Birth",accessorKey:"dob",footer:"Date of Birth",cell:(info)=>DateTime.fromISO(info.getValue()).toLocaleString(DateTime.DATE_MED),},]
Basically, this is how you can customize cells.
The function parameter is the object where you can have all the information regarding the particular cell.
Accessor Function - accessorFn
accessorKey is a static value property. But instead of static value, we can have dynamic value with accessorFn
Here, this creates 2 layer of header groups. This creates gibberish like the first group is going to have ID and Name. The second group is also going to have ID and then First and Last. This way it doesn’t serve our purpose. So, render them this way:
<thead>{table.getHeaderGroups().map((headerGroup)=>(<trkey={headerGroup.id}>{headerGroup.headers.map((header)=>(<thkey={header.id}>{header.isPlaceholder?null:flexRender(header.column.columnDef.header,header.getContext())}</th>
))}</tr>
))}</thead>;
/*
Parent HeaderGroup > -- Name
Child_ HeaderGroup > ID First Last
*/
Name header has child columns. These child columns merge with headers that don’t have child columns.
So, there will be placeholders in the first header group. The placeholders will have null values as it’s in the code.
Pagination
In order to have pagination, the first condition you have to set getPaginationRowModel in the useReactTable hook’s object.
Now, add pagination buttons right after the table elements:
......
...
These button events are pretty self-explanatory.
This way it creates a conflict. The conflict is when there are no data, it will still try to get the next data and obviously it will show no data or empty. So, disabling the buttons in such cases is the solution.
onClick={()=> table.setPageIndex(0)}>First page
disabled={!table.getCanPreviousPage()}onClick={()=> table.previousPage()}
>
Previous page
disabled={!table.getCanNextPage()}onClick={()=> table.nextPage()}>
Next page
onClick={()=> table.setPageIndex(table.getPageCount() - 1)}>
Last page
These methods are also very much self-explanatory.
Sorting
First of all set getSortedRowModel.
Now, set the sorting state (state property after getSortedRowModel).
Then, set ascending and descending sign right after every header value.
Then, add onClick={header.column.getToggleSortingHandler()} in header to change sorting.