usePgTable
The mount hook. Creates the store entry, wires state-listeners and persistence, and hands back a typed tableApi (carrying reactive useWatchData / useWatchState methods pre-bound to this table) plus the same hooks as destructurable bindings on the return. The single React-side ingress for a pgTable.
Signature
Three positional args — columns, state, and an options object. The options object covers identification, initial overrides, listeners, and persistence.
// signature usePgTable(columns, state, options?): { tableApi: PgTableApi<C, TDef> // imperative API; carries scoped useWatch* too useWatchData: () => PgTableData<C> // scoped, pre-typed, stable across renders useWatchState: ScopedUseWatchState<TDef> // scoped, pre-typed, stable across renders } type PgTableOptions<TDef, C> = { pgTableId?: string // caller-assigned id; auto-generated if omitted state?: Partial<PgTableStateValues<TDef['stateShape']>> // initial overrides, merged shallowly data?: PgTableData<C> // initial rows stateListeners?: { [K in keyof TDef['stateShape']]?: PgTableStateListener<C, TDef['stateShape']> } onStateChange?: (info: StateChangeInfo<TDef['stateShape'], C>) => void persist?: PgTablePersistConfig }
Simple-table usage
The simple-table tier puts the table-config inline in a single <name>-table.tsx. The component is the composition-component: it owns usePgTable, subscribes via watch hooks, and renders.
import z from 'zod' import { pgt, usePgTable } from '@westopp/pgtable' const charactersColumns = pgt.tableColumns()({ id: { type: z.string() }, name: { type: z.string() }, }) const charactersState = pgt.tableState( { sort: z.object({ by: z.string().nullable(), direction: z.enum(['asc', 'desc']) }) }, { sort: { by: 'name', direction: 'asc' } }, ) export const CharactersTable = () => { const { tableApi, useWatchData, useWatchState } = usePgTable(charactersColumns, charactersState, { data: [] }) const data = useWatchData() // pre-typed against charactersColumns const sort = useWatchState('sort') // pre-typed against charactersState return <div>{data.length} rows, sorted by {sort.by ?? 'nothing'}</div> }
Per-mount overrides
state overrides are merged shallowly over the declaration's initialState — useful for screen-specific defaults like a different pageSize. data is the seed for the rows; it is captured once on first render and never re-read.
usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', // stable id — survives unmount when persisting, // and is the id passed to getTable() outside React state: { pageSize: 10 }, // per-instance default data: seed, // initial rows (captured once on mount) })
State-listeners and onStateChange
Per-key stateListeners fire after a commit that touched that key, with three args: (newValue, prevState, currentData) — the new slice value, the full state object as it stood before the commit, and a getter for the live rows. To write back, capture tableApi from the destructure and call tableApi.setData / tableApi.setState from the listener body.
onStateChange fires once per commit and receives { state, prevState, propertiesUpdated, currentData } — the full new state, the full prev state, the list of keys that changed, and the same data getter.
// Trivial listeners stay inline — a one-line side effect, an obvious reset. // Anything with branching, multi-key updates, or fetching // moves to a named-table-state-listener — see Examples → Complex table. const { tableApi } = usePgTable(charactersColumns, charactersState, { pgTableId: 'characters', stateListeners: { sort: (next, _prevState, _currentData) => { tableApi.setState((prev) => { prev.pagination.page = 1 }) }, }, onStateChange: ({ propertiesUpdated, state, prevState }) => { analytics.track('table.state.change', { propertiesUpdated, state, prevState }) }, })
Initial values are captured once
initialState, initialData, and the resolved persist config are snapshotted at the first render of a given pgTableId. Subsequent renders that pass different values do not re-baseline the store — usePgTable is mount-time configuration.
// initialState, initialData, and the resolved persist config are captured // ONCE on the first render. Mutating these objects on later renders has no // effect — they are frozen into the store entry at create time. // // To replace rows after mount, call tableApi.setData(newRows). // To re-baseline dirtiness, see Guides → Dirty tracking and save (Rebasing).