Why pgTable
Six things pgTable gives you that you would otherwise stitch together by hand. Ordered from the most distinguishing to the least.
Most table libraries hand you primitives and let you wire the state. pgTable inverts that. You declare your state slices with pgt.tableState, and the store owns them from there. The table reads from the store; your widgets write to it; nothing else has to coordinate. Forget the row of useState calls, the reducers, and the context wrappers — the store is the wiring.
mode: 'session' | 'local' — pick a surface.onPersistSchemaMismatch migration hooks.Persistence is a persist option on usePgTable, not a plugin. Set mode: 'local' and the table writes itself to localStorage on every change; set mode: 'session' and it lives for the tab. Bump schemaVersion when your shape changes and you migrate inside the callback. No middleware, no separate store, no useEffect dance.
getTable(id) / setTableData(id, …) work outside React.Every table is reachable by its pgTableId. A toast handler can read the current selection; a router loader can prime the data; one table can react to another. The React hook is the convenient surface, but the store is the source of truth — and it doesn't care whether you're inside a component or not.
tableApi.useWatchData() for the rows.tableApi.useWatchState(key) for a single state slice.Data and state are tracked separately, and state is tracked per key. The pagination component subscribes to pagination. The sort dropdown subscribes to sort. The row grid subscribes to data. None of them re-render when the others change. You get the granularity of a hand-tuned store without writing one.
z.ZodType.pgt.tableColumns<Meta>()(cols) takes a Zod type per column plus your meta shape — labels, widths, cell renderers, anything you like. TypeScript infers the row type from the schemas, and parsing/validation come along for the ride. No duplicate definitions, no type drift between runtime and compile-time.
isDirtyData(), isDirtyState(), dirtyStateProperties() — public API.Save-on-dirty is a one-liner. Multi-table forms — a dashboard with three editable grids feeding one submit — work because every table is reachable from a single helper. Walk the global store, collect dirty slices, ship the payload. No Zustand wrapper, no Redux slice, no React Context provider tree.