TanStack Table Integration#
Zerobyte uses TanStack React Table (headless, @tanstack/react-table) to drive the Volumes, Repositories, and Notifications list pages. All three tables follow an identical controlled-state pattern: SortingState and ColumnFiltersState are held in local React useState, passed into useReactTable via the state object, and updated through onSortingChange / onColumnFiltersChange callbacks . Table state is not persisted across page reloads.
All three tables enable the same three row model factories:
| Row model | Purpose |
|---|---|
getCoreRowModel | Base row rendering |
getFilteredRowModel | Client-side column filtering |
getSortedRowModel | Client-side multi-column sorting |
The rendered output uses the shared Shadcn Table / TableHeader / TableBody primitives from ~/client/components/ui/table, iterated via table.getHeaderGroups() and table.getRowModel().rows with flexRender.
DataTableSortHeader Component#
Location: app/client/components/data-table-sort-header.tsx
This shared generic component handles the clickable sort-toggle header button used on every sortable column across all three tables. Its API:
DataTableSortHeader<TData, TValue>({
column, // Column<TData, TValue> from TanStack Table
title, // Display label (uppercased via CSS)
sortDirection, // column.getIsSorted() β false | "asc" | "desc"
center?, // Optional: positions the icon absolutely for centered columns
})
Behavior:
- Clicking calls
column.toggleSorting(column.getIsSorted() === "asc"), toggling between ascending and descending. - The icon is
ArrowUp(asc),ArrowDown(desc), orArrowUpDown(unsorted) from Lucide . - When unsorted, the
ArrowUpDownicon is hidden on lg+ screens and only revealed ongroup-hoverβ keeping headers uncluttered until the user hovers . - The
centerprop renders the icon absolutely positioned to the right of the title text, preventing layout shift in centered columns like Status .
Per-Table Column Definitions#
Each table declares its own ColumnDef<RowType>[] constant at module level. Key differences:
Volumes (volumeColumns)#
- Columns: Name (text filter on
name), Backend (exactfilterFnontype), Status (exactfilterFn, centered) . - Default sort state: empty β no initial sort applied .
- Filtering UI: text
<Input>for name search + two<Select>dropdowns for status (mounted/unmounted/error) and backend type .
Repositories (repositoryColumns)#
- Columns: Name, Backend (exact
filterFn), Compression (accessorFnwithalphanumericsort), Status (accessorFndefaulting to"unknown",alphanumericsort, exactfilterFn) . - Default sort state:
[{ id: "name", desc: false }]β pre-sorted ascending by name on load . - The Compression column is hidden on small screens (
hidden sm:table-cell) .
Notifications (notificationColumns)#
- Columns: Name, Type (exact
filterFn), Status (derived viaaccessorFn: getNotificationStatus, which returns"disabled"whenrow.enabledis false) . - Default sort state: empty .
- Status filter options include
"disabled", which is not a backend status value but is synthesized client-side .
Filter Controls & UX Patterns#
All three pages share the same filter UX conventions:
- Name/text search calls
table.getColumn("name")?.setFilterValue(e.target.value)directly β TanStack Table's built-in string filter handles the matching . - Dropdown filters (status, type/backend) use an exact-match
filterFn: (row, id, value) => row.getValue(id) === valuedefined on the column . - A "Clear filters" button appears conditionally when
columnFilters.length > 0and callstable.resetColumnFilters(). - An inline empty state ("No X match your filters") with a secondary clear button is rendered as a hidden
<TableRow>that becomes visible whenrows.length === 0 && data.length > 0. - The footer displays a live row count reflecting the current filtered result set .
Origin & Limitations#
Column sorting was introduced for Repositories and Volumes together in PR #808 β feat: add repositories & volumes column sorting. Notifications followed the same pattern when that page was built.
All sorting and filtering is performed client-side on data already fetched via TanStack Query (useSuspenseQuery). Table state (SortingState, ColumnFiltersState) lives in component-local useState and is reset on navigation β there is no URL synchronization or session persistence.