Basic React Table
Create an instance of a react-table.
const BasicTable = () => {
......
...
const table = useReactTable({ data, columns })
return (
<div></div>
)
}
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 toheader
, but it defines the text that appears at the bottom of the column as a footer.
Render table in jsx:
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody>
...
</tbody>
<tfoot></tfoot>
</table>
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:-
A string (e.g.,
"Name"
-> just displayed as-is). -
A function (e.g.,
({ column }) => <span>{column.id} Column</span>
-> needs context to work).
-
A string (e.g.,
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>
<thead>
...
</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>
<tfoot>
</tfoot>
</table>
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
<tfoot>
{table.getFooterGroups().map((footerGroup) => (
<tr key={footerGroup.id}>
{footerGroup.headers.map((header) => (
<th key={header.id}>
{flexRender(header.column.columnDef.footer, header.getContext())}
</th>
))}
</tr>
))}
</tfoot>
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:
<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>;
/*
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.
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
Now, add pagination buttons right after the table elements:
<table>
......
...
</table>
<div>
<button onClick={() => table.setPageIndex(0)}>First page</button>
<button onClick={() => table.previousPage()}>Previous page</button>
<button onClick={() => table.nextPage()}>Next page</button>
<button onClick={() => table.setPageIndex(table.getPageCount() - 1)}>
Last page
</button>
</div>
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.
<div>
<button onClick={() => table.setPageIndex(0)}>First page</button>
<button
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
>
Previous page
</button>
<button disabled={!table.getCanNextPage()} onClick={() => table.nextPage()}>
Next page
</button>
<button onClick={() => table.setPageIndex(table.getPageCount() - 1)}>
Last page
</button>
</div>
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.
const [sorting, setSorting] = useState([]);
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
state: {
sorting: sorting,
},
onSortingChange: setSorting,
});
return (
<div>
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{
{ asc: "↑", desc: "↓" }[
header.column.getIsSorted() ?? null
]
}
</div>
)}
</th>
))}
</tr>
))}
</thead>
</table>
</div>
);
TanStack Table uses ‘asc’ and ‘desc’ by default for sorting. So, the sorting mechanism here is pretty straight-forward.
Filtering
set getFilteredRowModel()
,
const table = useReactTable({
......
...,
getFilteredRowModel: getFilteredRowModel(),
});
Here, we need to set filtering state as well.
const [filtering, setFiltering] = useState('')
const table = useReactTable({
......
...,
state: {
...,
globalFilter: filtering
},
onGlobalFiltering: setFiltering
});
This globalFilter setting makes a searching mechanism for data table.
Now, add a search input right before the table:
<>
<input
type="text"
value={filtering}
onChange={(e) => setFiltering(e.target.value)}
/>
<table></table>
</>
Top comments (0)