table

Show rows of data with sorting, paging and filters that stay in the URL.

InterfaceClientBuilt onTanStack Table
/play/table/sortable-orders
Click a header to sort, click again to reverse, then press Reload: the sort comes back from the URL. Status is not sortable.

What it is

@kitr/table is TanStack Table plus URL state. useUrlTableState() keeps sorting, paging and filters in the route's search params; parseTableSearch() validates them, so ?page=abc becomes page 1. Rows come from the server through query.

Runs in the browser. Made for router's validateSearch and navigate.

Install

kitr add table

First time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.

Adds @kitr/table and TanStack Table. No provider, no config file. Every change.

Quick start

Three pieces: columns, a route that accepts table params, and the table. The columns:

// app/features/orders/columns.tsx
import type { ColumnDef, DataTableFeatures } from '@kitr/table'

export interface Order {
  id: string
  customer: string
  total: number
  status: 'open' | 'paid' | 'refunded'
}

export const columns: ColumnDef<DataTableFeatures, Order>[] = [
  { accessorKey: 'customer', header: 'Customer' },
  { accessorKey: 'total', header: 'Total', cell: ({ row }) => `€${row.original.total.toFixed(2)}` },
  { accessorKey: 'status', header: 'Status', enableSorting: false },
]

validateSearch: parseTableSearch on the route makes page, size, sort and filter typed search params. The page turns them into table state and hands them to the registry's <DataTable>:

// app/pages/orders/index.tsx
import { useSuspenseQuery } from '@kitr/query'
import { getRouteApi } from '@kitr/router'
import { useUrlTableState } from '@kitr/table'
import { DataTable } from '#components/ui/data-table'
import { columns } from '#app/features/orders/columns'
import { ordersPageQuery } from '#app/features/orders/queries'

const route = getRouteApi('/orders')

export default function OrdersPage() {
  const search = route.useSearch()
  const navigate = route.useNavigate()
  const { data } = useSuspenseQuery(ordersPageQuery.options(search))
  const urlState = useUrlTableState({
    search,
    setSearch: (patch) => navigate({ search: (prev) => ({ ...prev, ...patch }) }),
  })
  return <DataTable columns={columns} data={data.rows} rowCount={data.total} {...urlState} />
}

Click the Total header. The URL becomes /orders?sort=-total, the loader fetches that page, and the table shows the largest orders first. Reload and it stays sorted. Next in the guide: Show your first sortable table.

Built on

kitr adds the URL state hook, the search param parser and the feature set for the data table component.

Problems it solves

Further reading