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 />);