What is the best practice to fetch data for Tanstack table? I have a function in the backend (NodeJS) which receives page number and page size and returns an array of data.
I want to fetch the data again when changing the page number or page size, but to not use states in the parent component. Or is it better to do that using the front end?
The page.tsx looks like this:
import { Trainee, columns } from "./columns"
import DataTable from "./data-table"
async function getTraineeData(page: number, pageSize: number): Promise<Trainee[]> {
try {
const response = await fetch(`....(fetch url)/${page}&limit=${pageSize}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('An error occurred while trying to fetch table data');
}
const data = await response.json();
console.log('data', data)
return data;
}
catch (err) {
console.log(err);
return [];
}
}
const TraineeTable = async () => {
const data = await getTraineeData(0, 10);
return (
<div className="container mx-auto py-10">
<DataTable columns={columns} data={data} />
</div>
)
}
export default TraineeTable;
then child component DatTtable
looks like this:
'use client'
import { ColumnDef, flexRender, useReactTable, getCoreRowModel, getPaginationRowModel, SortingState, getSortedRowModel, ColumnFiltersState, getFilteredRowModel, VisibilityState } from '@tanstack/react-table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import * as React from "react"
import { Trainee, columnTranslations } from './columns';
import { BiChevronDown } from 'react-icons/bi';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
const DataTable = <TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) => {
const [sorting, setSorting] = React.useState<SortingState>([])
const [pageSize, setPageSize] = React.useState(10);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
)
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({})
const [rowSelection, setRowSelection] = React.useState({})
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
})
return (
<div className='w-full'>
<div className="flex items-center py-4 gap-5">
<Input
placeholder="פילטור לפי שם"
value={(table.getColumn("fullName")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("fullName")?.setFilterValue(event.target.value)
}
className="max-w-sm rounded"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto rounded">
<BiChevronDown className="ml-2 h-4 w-4" />columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter(
(column) => column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
dir="rtl"
>
{columnTranslations[column.id] || column.id}
</DropdownMenuCheckboxItem>
)
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border px-2">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
Nothing to show here.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{table.getFilteredSelectedRowModel().rows.length > 0 && (<div className="flex-1 mt-2 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} lines selected
</div>)
}
<div className="flex items-center justify-center gap-5 space-x-2 py-4">
<Button
variant="outline"
size="sm"
className="text-green2 cursor-pointer"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
previous
</Button>
<Button
variant="outline"
size="sm"
className="text-green2 cursor-pointer"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
next
</Button>
<span className="flex items-center gap-1">
<div>page</div>
<strong>
{table.getState().pagination.pageIndex + 1}</strong> מתוך{" "}
<strong>{table.getPageCount()}</strong>
</span>
<span className="flex items-center gap-1">
move to page:
<input
type="number"
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
table.setPageIndex(page);
}}
className="border p-1 rounded w-16 bg-transparent"
/>
</span>
<select
value={table.getState().pagination.pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
table.setPageSize(Number(e.target.value));
}}
className="p-2 bg-transparent"
>
{[10, 20, 30, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
show {pageSize}
</option>
))}
</select>
</div>
</div>
)
};
export default DataTable;
I am confused how to do it correctly.