Using React Table
It's possible to use @tanstack/react-table with the Kyber Table component to create powerful data tables with sorting, filtering, pagination, and selection capabilities. Below are some examples demonstrating how to implement these features. For more in depth examples refer to the React Table's documentation.
Sorting
Kyber's TableSortLabel component can be used to create sortable table headers.
const sortStatusFn: SortingFn<Person> = (rowA, rowB, _columnId) => { const statusA = rowA.original.status const statusB = rowB.original.status const statusOrder = ['single', 'complicated', 'relationship'] return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB) }; function Example() { const [sorting, setSorting] = React.useState([]); const columns = React.useMemo( () => [ { accessorKey: 'firstName', header: () => 'First Name', }, { accessorKey: 'lastName', header: () => 'Last Name', }, { accessorKey: 'age', header: () => 'Age', }, ], [] ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 24 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, ]); const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onSortingChange: setSorting, state: { sorting, }, }); return ( <Table> <TableHeader> {table.getHeaderGroups().map(headerGroup => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map(header => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( <TableSortLabel sortDirection={ header.column.getIsSorted() === 'asc' ? 'asc' : header.column.getIsSorted() === 'desc' ? 'desc' : undefined } onClick={header.column.getToggleSortingHandler()} > {flexRender( header.column.columnDef.header, header.getContext() )} </TableSortLabel> )} </TableCell> ) })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map(row => { return ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => { return ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ) })} </TableRow> ) })} </TableBody> </Table> ); } render(<Example />);
Filtering
Custom filter UIs can be created to set the column filters state in React Table. In this example, Tabs are used to filter rows based on age. There are 10 built-in filter functions available in react-table including 'inNumberRange' which is used in this example.
function Example() { const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]) const columns = React.useMemo( () => [ { accessorKey: 'firstName', header: () => 'First Name', }, { accessorKey: 'lastName', header: () => 'Last Name', }, { accessorKey: 'age', header: () => 'Age', filterFn: 'inNumberRange', }, ], [] ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 25 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, ]); const table = useReactTable({ data, columns, filterFns: {}, state: { columnFilters, }, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); return ( <> <div className="k-py-3"> <Tabs variant="pills" tabs={[ <Tab key="all" id="all" onClick={() => setColumnFilters([])}> All </Tab>, <Tab key="over-25" id="over-25" onClick={() => setColumnFilters([{ id: 'age', value: [ 26 ] }])}> Over 25 </Tab> ]}/> </div> <Table> <TableHeader> {table.getHeaderGroups().map(headerGroup => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map(header => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( flexRender( header.column.columnDef.header, header.getContext() ) )} </TableCell> ) })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map(row => { return ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => { return ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ) })} </TableRow> ) })} </TableBody> </Table> </Button> ); } render(<Example />);
Using filter components
It's also possible to create complex filters with Kyber's filter components. In this example, a NumberFilter component is used for filtering the age column.
function Example() { const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]) const columns = React.useMemo( () => [ { accessorKey: 'firstName', header: () => 'First Name', }, { accessorKey: 'lastName', header: () => 'Last Name', }, { accessorKey: 'age', header: () => 'Age', filterFn: 'inNumberRange', }, ], [] ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 25 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, ]); const table = useReactTable({ data, columns, filterFns: {}, state: { columnFilters, }, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); return ( <> <div className="d-flex justify-content-end k-py-3"> <NumberFilter filterName="Age" onApply={(event, { value, operator }) => { let nextValue; switch(operator) { case 'eq': nextValue = [value, value]; break; case 'gte': nextValue = [value]; break; case 'lte': nextValue = [0, value]; break; case 'between': nextValue = [value.startValue, value.endValue] break; default: nextValue = []; break; } setColumnFilters([{ id: 'age', value: nextValue }]) }} /> </div> <Table> <TableHeader> {table.getHeaderGroups().map(headerGroup => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map(header => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( flexRender( header.column.columnDef.header, header.getContext() ) )} </TableCell> ) })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map(row => { return ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => { return ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ) })} </TableRow> ) })} </TableBody> </Table> </Button> ); } render(<Example />);
Searching
In this example a rudimentary search can be performed using the filtering mechanism of React Table combined with the Search component. More complex fuzzy searching can be implemented using custom filter functions.
const filterOptions = [ { name: 'First Name', value: 'firstName', }, { name: 'Last Name', value: 'lastName', } ]; function Example() { const [selectedSearchFilter, setSelectedSearchFilter] = React.useState<string | undefined>('firstName'); const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]) const columns = React.useMemo( () => [ { accessorKey: 'firstName', header: () => 'First Name', filterFn: 'includesString', }, { accessorKey: 'lastName', header: () => 'Last Name', filterFn: 'includesString', }, { accessorKey: 'age', header: () => 'Age', }, ], [] ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 25 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, ]); const table = useReactTable({ data, columns, filterFns: {}, state: { columnFilters, }, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel(), }); return ( <> <div className="d-flex justify-content-end k-py-3"> <Search style={{ minWidth: 300, maxWidth: 300}} filterOptions={filterOptions} filterTitle={filterOptions.find((filter) => filter.value === selectedSearchFilter).name} onFilterChange={setSelectedSearchFilter} onChange={(value: string) => { if (value === '') { setColumnFilters([]); } else { setColumnFilters([{ id: selectedSearchFilter, value: value, }]); } }} /> </div> <Table> <TableHeader> {table.getHeaderGroups().map(headerGroup => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map(header => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( flexRender( header.column.columnDef.header, header.getContext() ) )} </TableCell> ) })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map(row => { return ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => { return ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ) })} </TableRow> ) })} </TableBody> </Table> </Button> ); } render(<Example />);
Pagination
Either Pagination or PaginationByRows components can be used to control the pagination state of React Table. In this example, PaginationByRows is used to control the page index and page size.
const filterOptions = [ { name: 'First Name', value: 'firstName', }, { name: 'Last Name', value: 'lastName', } ]; function Example() { const [pagination, setPagination] = React.useState<PaginationState>({ pageIndex: 0, pageSize: 4, }) const columns = React.useMemo( () => [ { accessorKey: 'firstName', header: () => 'First Name', }, { accessorKey: 'lastName', header: () => 'Last Name', }, { accessorKey: 'age', header: () => 'Age', }, ], [] ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 25 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, { firstName: 'tyler', lastName: 'mcginnis', age: 35 }, { firstName: 'dan', lastName: 'abramov', age: 30 }, { firstName: 'kent', lastName: 'c. dodds', age: 33 }, { firstName: 'sebastian', lastName: 'markbåge', age: 29 }, { firstName: 'sophia', lastName: 'alvarez', age: 28 }, { firstName: 'liam', lastName: 'nguyen', age: 31 }, { firstName: 'olivia', lastName: 'garcia', age: 27 }, { firstName: 'noah', lastName: 'martinez', age: 26 }, ]); const table = useReactTable({ columns, data, debugTable: true, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), onPaginationChange: setPagination, state: { pagination, }, }); return ( <> <Table> <TableHeader> {table.getHeaderGroups().map(headerGroup => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map(header => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : ( flexRender( header.column.columnDef.header, header.getContext() ) )} </TableCell> ) })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map(row => { return ( <TableRow key={row.id}> {row.getVisibleCells().map(cell => { return ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ) })} </TableRow> ) })} </TableBody> </Table> <div className="k-py-3"> <PaginationByRows rowsPerPageOptions={[4, 8, 12]} rows={data.length} activePage={pagination.pageIndex + 1} rowsPerPage={pagination.pageSize} onPageClick={(e, { nextActivePage }: number) => { setPagination((old) => ({ ...old, pageIndex: nextActivePage - 1, })); }} onChangeRowsPerPage={(e, { nextRowsPerPage, nextActivePage }: number) => { setPagination((old) => ({ ...old, pageIndex: nextActivePage - 1, pageSize: nextRowsPerPage, })); }} /> </div> </div> ); } render(<Example />);
Selection
Using Kyber's Checkbox component along with React Table's row selection API, selectable rows can be implemented. In this example, a "Select All" checkbox is included in the table header, and individual checkboxes are provided for each row. Additionally, a DropdownButton is used to perform actions on the selected rows. See row selection documentation for more details.
function Example() { const [rowSelection, setRowSelection] = React.useState({}); const columns = React.useMemo( () => [ { id: 'select', header: ({ table }) => ( <Checkbox aria-label="Select All Rows" {...{ isSelected: table.getIsAllRowsSelected(), isIndeterminate: table.getIsSomeRowsSelected(), onChange: table.toggleAllRowsSelected, }} /> ), cell: ({ row }) => ( <Checkbox aria-label="Select Row" {...{ isSelected: row.getIsSelected(), disabled: !row.getCanSelect(), isIndeterminate: row.getIsSomeSelected(), onChange: row.getToggleSelectedHandler(), }} /> ), }, { accessorKey: 'firstName', header: () => 'First Name', }, { accessorKey: 'lastName', header: () => 'Last Name', }, { accessorKey: 'age', header: () => 'Age', }, ], [], ); const [data, setData] = React.useState(() => [ { firstName: 'tanner', lastName: 'linsley', age: 25 }, { firstName: 'derek', lastName: 'perkins', age: 40 }, { firstName: 'joe', lastName: 'bergevin', age: 45 }, ]); const table = useReactTable({ data, columns, state: { rowSelection, }, enableRowSelection: true, onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), }); return ( <> <style> {` .selection-table thead tr th:first-child { width: 0%; } .selection-table .form-check { margin-right: var(--k-spacer-n2); margin-bottom: 0px; } `} </style> <div className="d-flex justify-content-end k-py-3"> <DropdownButton title="Selection Actions" variant="outline-secondary"> <DropdownItem onClick={() => { const selectedRowsById = table.getSelectedRowModel().rowsById; setData((old) => { return old.map((row, index) => { if (selectedRowsById[index]) { return { ...row, firstName: '🤠', }; } return row; }); }); }} > Howdy </DropdownItem> </DropdownButton> </div> <Table className="selection-table"> <TableHeader> {table.getHeaderGroups().map((headerGroup) => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map((header) => { return ( <TableCell key={header.id} colSpan={header.colSpan}> {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} </TableCell> ); })} </TableRow> ))} </TableHeader> <TableBody> {table .getRowModel().rows .map((row) => { return ( <TableRow key={row.id}> {row.getVisibleCells().map((cell) => { return ( <TableCell key={cell.id}> {flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell> ); })} </TableRow> ); })} </TableBody> </Table> </style> ); } render(<Example />);