tableApi
The handle returned by usePgTable. Read rows and state, mutate them, query dirtiness, drop the entry — plus reactive useWatchData / useWatchState methods that are pre-bound to this table and pre-typed against its columns and state shape. Imperative calls hit the store directly; reactive calls subscribe through useSyncExternalStore. Forward tableApi through a table-hook or pass it as a prop — the inner component reads slices via tableApi.useWatchData() / tableApi.useWatchState(key), no id threading or generic restatement required.
Full surface
Reads, writes, dirty queries, lifecycle — flat, no chaining. Each method either reads the committed store or commits a new value and triggers per-slice notifications.
// All methods on PgTableApi<C, S>. The second generic accepts the full // state definition (the return of `pgt.tableState(...)`) — i.e. `typeof state`, // not `typeof state.stateShape`. The shape is extracted internally. type PgTableApi<C, S> = { pgTableId: string // Imperative reads — no React lifecycle, no memoisation. getData(): PgTableData<C> getColumns(): C getColumnsArray(): PgTableColumnsArray<C> getState(): PgTableStateValues<S['stateShape']> getState<K>(key: K): PgTableStateValues<S['stateShape']>[K] lastUpdatedAt(): number | null // Reactive reads — subscribe in render bodies, re-render on slice change. // Pre-bound to pgTableId, pre-typed against C / S['stateShape']. No id arg, // no generic restatement at the call site. useWatchData(): PgTableData<C> useWatchState(): PgTableStateValues<S['stateShape']> useWatchState<K>(key: K): PgTableStateValues<S['stateShape']>[K] // Dirty. isDirtyData(): boolean isDirtyState(): boolean dirtyStateProperties(): Partial<PgTableStateValues<S['stateShape']>> // Writes — value or immer-style updater. setData(next): void appendRows(rows): void clearData(): void resetData(): void setState(next): void resetState(): void // Lifecycle. remove(): void }
Reads
Two read modes, both keyed off the same tableApi. Imperative reads (getData, getState(key)) are a fresh look at the store on every call — use them in event handlers, effect bodies, and listener bodies. Reactive reads (useWatchData(), useWatchState(key)) subscribe and re-render the calling component on every committed change to that slice — use them in render bodies, never in handlers. The use* prefix is the signal; the get* prefix is the signal — same rule as anywhere else in React.
// Imperative reads — committed values, no subscription. Safe in handlers, // effects, and listener bodies; useless in render bodies (no re-render). const rows = tableApi.getData() // PgTableData<C> const columns = tableApi.getColumns() // raw declaration object const columnsArr = tableApi.getColumnsArray() // [{ columnName, columnInfo }, ...] const allState = tableApi.getState() // every key const onlySort = tableApi.getState('sort') // one key, narrowly typed const lastUpdate = tableApi.lastUpdatedAt() // ms epoch | null // Reactive reads — subscribe in render bodies, re-render on slice change. // Already typed against this table's columns and state — no generic restatement. const liveRows = tableApi.useWatchData() // PgTableData<C> const liveSort = tableApi.useWatchState('sort') // PgTableStateValues<S['stateShape']>['sort'] const liveState = tableApi.useWatchState() // full state object; renders on any change
Writes
setData and setState both accept either a value or an updater function. Updaters get an immer draft — they can either return a new value or mutate the draft in place; pgTable accepts either. Every commit fires exactly the state-listeners affected by the changed keys.
// setData — pass a value, an updater that returns a new array, or an // updater that mutates the immer draft. tableApi.setData(newRows) tableApi.setData((rows) => rows.filter((r) => !banned.has(r.id))) // returns tableApi.setData((rows) => { rows.push(newRow) }) // mutates // appendRows — sugar for "extend the current array". tableApi.appendRows([row1, row2]) // clearData — write the empty array. Still counts as a dirty write. tableApi.clearData() // resetData — write the captured initialData back. tableApi.resetData() // setState — Partial value OR updater (return new value or mutate draft). tableApi.setState({ page: 1 }) tableApi.setState((s) => { s.pagination.page = 1 }) // resetState — write initialState back across every key. tableApi.resetState()
The updater context
The second arg to a setData / setState updater exposes committed reads — useful when the new value depends on the other slice. Reads from ctx always reflect the committed store, not the in-flight draft.
// The updater's 2nd arg gives committed reads (data/state/columns) — // useful when one write depends on the other slice's value. tableApi.setData((rows, ctx) => { const selected = ctx.getState('selectedIds') for (const r of rows) if (selected.includes(r.id)) r.starred = true }) tableApi.setState((s, ctx) => { // Recompute total from committed rows + draft pageSize. s.pagination.total = ctx.getData().length })
Dirty
isDirtyData / isDirtyState diff the current values against the captured initials. dirtyStateProperties returns the subset of state keys that differ — the convenient payload for a save endpoint that only takes changed fields.
// Dirtiness is calculated on every call — the store does not maintain a // "dirty" flag. Each method diffs against the captured initials. if (tableApi.isDirtyData() || tableApi.isDirtyState()) { await api.save({ rows: tableApi.getData(), changes: tableApi.dirtyStateProperties(), // only keys that differ from initial }) }
Lifecycle
remove() drops the in-memory entry only. Persisted entries on webstorage are separate; reach for dropTableEverywhere() from the global-helpers when you want both gone.
// remove() drops the in-memory store entry. Persisted webstorage is // untouched — use dropTableEverywhere() if you want both gone. tableApi.remove()