Skip to main content

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.

Result
Loading...
Live Editor
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.

Result
Loading...
Live Editor
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.

Result
Loading...
Live Editor
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.

Result
Loading...
Live Editor
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.

Result
Loading...
Live Editor
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.

Result
Loading...
Live Editor
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 />);