Table sorting
The press cycle a sortable header reports
source order — press a header
Using TanStack Table.
Usage
import { TableHeader, TableRow, TableSortHeader, useTable } from "@gpuixui/data";
const table = useTable({ rows, keyOf: (row) => row.id, columns });
<TableHeader>
<TableRow>
<TableSortHeader
field="status"
label="Status"
sort={table.sort}
order={table.sortOrder}
onSortPress={table.pressSort}
width={110}
/>
<TableSortHeader
field="name"
label="Name"
sort={table.sort}
order={table.sortOrder}
onSortPress={table.pressSort}
width={260}
/>
</TableRow>
</TableHeader>;API
| Part | Prop | Type | Default | Consequence |
|---|---|---|---|---|
TableSortHeader | field | string | — | the column the press sorts by |
TableSortHeader | label | string | — | the visible label, and the base of the announced name; a blank label reads as field, so a sortable column is never unnamed |
TableSortHeader | sort | Option<SortState> | — | the primary sort, for the glyph and the single-sort callback |
TableSortHeader | order | SortOrder | sort alone | the whole order; a multi-entry order adds each field's place to the glyph and the name |
TableSortHeader | onSortPress | (field, initialDirection, modifiers?) => void | — | the raw press, for a controller that composes an order (useTable().pressSort) |
TableSortHeader | onSort | (next: SortState) => void | — | the single-column alternative: the next entry the press cycle produces |
TableSortHeader | initialDirection | "asc" | "desc" | "asc" | the direction a first press starts at |
TableSortHeader | width | number | — | a fixed track; a resizable column needs one |
TableSortHeader | align | "left" | "center" | "right" | "left" | where the label sits across the track |
TableSortHeader | testId | string | — | the header cell's test id; the press control is ${testId}-sort |
TableSortHeader | resize | TableColumnResizeProps | — | the divider's bounds and callback |
useTable | sort | Option<SortState> | — | the primary sort |
useTable | sortOrder | SortOrder | [] | every field in the order, primary first; an entry naming a column this grid does not hold is not counted |
useTable | pressSort | (field, initialDirection, modifiers?) => void | — | the order a header press produces; Shift adds a tie-break |
useTable | toggleSort | (field, additive?) => void | — | the same cycle without the runtime's modifiers |
useTable | setSort | (next: Option<SortState>) => void | — | replace the order with one field, or clear it |
useTable | save | () => GridConfig | — | the record to keep: the order the grid runs, the query and the core's chosen ids |
A saved order is read
A store-driven grid builds its table with constructGrid, so the state it
restores — a workspace's sorting, its query, its chosen ids — arrives as data
rather than as React state. JSON writes a field that was never set as null,
and a release that renames a column leaves the entry it named behind, so the
state is read before the core sees it: readGridState(stored) answers the three
slices as the shapes they mean, and constructGrid reads its own state the
same way.
- a sorting list that is not a list is no sort (the source order), and an entry that names no column is dropped;
- a query that is not text is no query — nothing typed narrowed the set;
- a chosen set keeps only the ids the store set to
true.
A saved order can also name a column this release dropped: the row model
skips an entry it has no column to compare, and the order the grid reports —
sortOrder, and every header's "sort N of M" — drops the entry too, because a
sort that cannot run is not a sort. A column this grid still holds keeps its
entry whether or not it is hidden.
Measured before the read: globalFilter: null threw Cannot read properties of null (reading 'trim') out of the first build, sorting: null threw
state.sorting is not iterable, and a [null] entry threw Cannot read properties of null (reading 'id') — a grid that remembered its state could not
reopen at all. The same rule covers hiddenColumns: a stored list that is not a
list of text ids hides nothing.
import { constructGrid, readGridState } from "@gpuixui/data";
// `saved` is whatever the store handed back.
const table = constructGrid({
rows,
keyOf: (row) => row.id,
columns,
state: readGridState(saved),
onSortingChange: setSorting,
onGlobalFilterChange: setQuery,
onRowSelectionChange: setChosen,
});Saving the state a grid holds
gridConfig(state) writes the record readGridState reads, and
useTable().save composes it from the controller — the shape a workspace keeps
beside columnConfig's:
import { useTable } from "@gpuixui/data";
const table = useTable({ rows, keyOf: (row) => row.id, columns, stored });
// wherever the workspace persists its state
const saved = table.save();
// { sorting: [{ id, desc }], globalFilter, rowSelection: { [id]: true } }The record is written from the order the grid runs, so an entry naming a
dropped column is not persisted back, and from the core's chosen ids rather
than the retained view: a filter that hides a chosen record keeps it in the
core, so a grid saved while narrowed restores that record when the query widens
again. The range anchor is a gesture cursor, not state — it starts fresh at the
next press after a restore. save returns its own arrays and record, so a store
can serialise the value without aliasing what the grid paints from.
nub run --filter @gpuixui/data test:behaviour measures the round trip in the
renderer: a grid sorted by name, a chosen record, and a query that hides it are
saved and reopened as they were left.
The rows a store hands back
rows is stored data like the record beside it: a workspace restores a grid's
records from JSON, and an app passes snapshot?.rows while the snapshot is still
absent — undefined at runtime, though the prop type says a list is present.
useTable and constructGrid read the list through listOf, so a value that is
not a list is no rows: the grid paints 0 rows and its own empty state,
where the core's data access used to throw
Cannot read properties of null (reading 'length') out of the first build. A
readable list is the caller's own, untouched
(behaviour/stored-rows.test.tsx).
Edit this page on GitHubpackages/data/docs/table-sorting.mdx