pgt.tableState
Declares the state shape (a record of Zod schemas) plus an exhaustive initial value for each key. The return value is what you pass to usePgTable as the second argument — pgTable captures the shape and the initials at mount time.
Signature
Two arguments, returned as one object: stateShape for the per-key Zod schemas and initialState for the seed values. TypeScript pins the initials to match the schemas — change one key's type and the initial value for that key fails to compile.
// signature pgt.tableState<T extends Record<string, z.ZodType>>( stateShape: T, initialState: { [K in keyof T]: z.infer<T[K]> }, ): { stateShape: T; initialState: typeof initialState }
Moderate-table usage
The state shape is declared exactly once per table, at module scope, in the <name>-table-config.ts. Anything that fits in a Zod schema is fair game — primitives, nested objects, unions, arrays.
import z from 'zod' import { pgt } from '@westopp/pgtable' export const charactersState = pgt.tableState( { sort: z.object({ by: z.string().nullable(), direction: z.enum(['asc', 'desc']) }), query: z.string(), page: z.number(), pageSize: z.number(), selectedIds: z.array(z.string()), }, { sort: { by: 'name', direction: 'asc' }, query: '', page: 1, pageSize: 25, selectedIds: [], }, )
Overriding initials at mount
usePgTable accepts a state option that is a Partial of the state values — useful when the same table-config ships with different defaults across screens (a 25-row list view vs. a 5-row sidebar preview). Partial overrides merge shallowly over the declaration's initials.
// initialState in the declaration is exhaustive — every key in stateShape // must appear with a value. Partial overrides happen later, on usePgTable. const { tableApi } = usePgTable(charactersColumns, charactersState, { state: { pageSize: 50, sort: { by: 'power', direction: 'desc' } }, // ^ Partial<PgTableStateValues<typeof charactersState.stateShape>> })
Slice grouping shapes re-render behaviour
Subscribers wake per top-level key. Group things that change together (page + pageSize + total) under a single key so they emit one update; keep things that change independently (sort vs. filter) on their own keys so render-components subscribe narrowly.
// Group keys that change together. The store notifies subscribers per // top-level key, so co-locating page + pageSize + total under a single // 'pagination' slice means the pager re-renders once for a page change, // not three times. import z from 'zod' import { pgt } from '@westopp/pgtable' export const charactersState = pgt.tableState( { pagination: z.object({ page: z.number().int().min(1), pageSize: z.number().int().positive(), total: z.number().int().min(0), }), sort: z.object({ by: z.string().nullable(), direction: z.enum(['asc', 'desc']) }), loading: z.boolean(), }, { pagination: { page: 1, pageSize: 25, total: 0 }, sort: { by: null, direction: 'asc' }, loading: false, }, )
Sharing state-key shapes via a state-registry
When the same state-key shape appears in two or more table-configs with the same semantic meaning — sort, pagination, selectedIds, loading, fetchError — hoist it to a state-registry and reference it back from each config. pgt.stateRegistry({...}) describes shape only; initial values stay with the consuming table-config because they are instance-specific.
// Reusable state-key shapes shared across table-configs. Single-use shapes // stay inline in their table-config; the registry is for what is reused. import z from 'zod' import { pgt } from '@westopp/pgtable' export const sharedStateRegistry = pgt.stateRegistry({ sort: z.object({ by: z.string().nullable(), direction: z.enum(['asc', 'desc']) }), pagination: z.object({ page: z.number(), pageSize: z.number(), total: z.number() }), selectedIds: z.array(z.string()), loading: z.boolean(), fetchError: z.string().nullable(), })