pgTable/API Reference/useWatchData
API ReferenceHook

useWatchData

The standalone by-id subscription. Pass the pgTableId string and get back the current rows; re-renders only when data changes. Used where you don't have tableApi in scope — sibling consumers, multi-renderer per TABLE-217, anything reaching in by id. Where tableApi is in scope, prefer tableApi.useWatchData() (same hook, no id arg, no generic restatement).

Signature

One overload. Pass the pgTableId — the same id usePgTable mounted with — and an optional column-type generic so the return type is narrowed to PgTableData<C>.

TSsignature.ts
// Imported from @westopp/pgtable
useWatchData<C extends PgTableColumns>(pgTableId: string): PgTableData<C>

When to reach for the standalone hook

Prefer tableApi.useWatchData() inside any component that already has a typed tableApi — same hook, scoped to the right table, pre-typed against the columns, no generic restatement. The standalone useWatchData(id) is the answer when you only have a pgTableId string: sibling consumers that aren't part of the outer/inner split, multi-renderer setups (TABLE-217), a row-count badge mounted above the table.

TSXcharacters-row-count.tsx
// Standalone by-id read — used when tableApi isn't in scope. Pass the
// pgTableId as a prop and subscribe directly.
import { useWatchData } from '@westopp/pgtable'

export const CharactersRowCount = ({ pgTableId }: { pgTableId: string }) => {
const data = useWatchData(pgTableId)
return <span>{data.length} rows</span>
}

Derive view shapes on top

useWatchData returns the raw rows. Filtering, sorting, paginating, grouping are your useMemo calls downstream. The hook re-renders only when rows commit; your derivations re-run only when their actual inputs change.

TSXcharacters-table.tsx
// The hook returns the raw rows. Anything view-shaped (filtered,
// sorted, paginated) is your useMemo over it. pgTable does not ship a
// query engine — that is a feature.
//
// Inside an inner component that has tableApi in scope, prefer the
// scoped methods — already typed against this table.

const data  = tableApi.useWatchData()
const query = tableApi.useWatchState('query')

const visible = useMemo(() => {
const q = query.trim().toLowerCase()
if (!q) return data
return data.filter((r) => r.name.toLowerCase().includes(q))
}, [data, query])

Reads are immutable to the caller

The array returned reflects the committed store. Mutating it in place — data[0].name = 'x' — does not notify subscribers and corrupts the store's view of "what is current" for the next reader. Writes go through tableApi.setData (or the global setTableData).

TSimmutable-reads.ts
// Reads from useWatchData are read-only by convention.
const data = useWatchData(pgTableId)

// BAD — silent no-op for subscribers, leaves the store inconsistent.
// data[0].name = 'edited'

// GOOD — explicit write through setData; subscribers wake.
// tableApi.setData((rows) => { rows[0].name = 'edited' })

Behaviour when the table isn't mounted

Reading from a pgTableId that does not exist returns a stable empty array. No throw, no warning — a deliberately lenient choice to keep out-of-order mounts from cascading errors.

TSmissing.ts
// If the pgTableId does not exist in the store yet, useWatchData returns
// a stable frozen empty array — no throw, no flicker. Once the table
// mounts, the subscription begins delivering real values.
//
// This is intentional: render-order between an outer that mounts the
// store and a sibling consumer isn't always guaranteed.