pgTable/Guides/Reading tables outside React
Guides~5 min

Reading tables outside React

Every read and write on tableApi has a global-helper equivalent, addressed by pgTableId. Route loaders, websocket handlers, toast actions, sign-out flows — anything that runs outside a component can read and write the same store as the hook. The global-helpers are caller-agnostic: they take the inputs they need through params, no provider tree, no context.

The outside-React surface

Eight functions cover the imperative API: existence checks, reads, writes, removal, plus the persistence inspect/clear trio. Reads return null when the entry doesn't exist; writes against a missing entry are no-ops (they don't throw and don't create the entry — the hook owns creation).

TSoutside-react-surface.ts
import {
hasTable,
getTable,
getTableData,
getTableState,
setTableData,
setTableState,
removeTable,
// Persistence inspect/clear:
getPersistedTableProps,
clearStoredTables,
dropTableEverywhere,
} from '@westopp/pgtable'

hasTable('characters')                              // boolean
getTableData('characters')                          // Row[] | null
getTableState('characters')                         // { ... } | null
getTable('characters')                              // PgTableApi | null

setTableData('characters', rows)                    // value OR updater
setTableState('characters', { loading: false })     // partial OR updater

removeTable('characters')                           // drop in-memory entry

Priming from a route loader

Tanstack Router and React Router loaders run before the component mounts. Use them to seed data into the store so the rendered route doesn't trigger an initial fetch. The store entry only exists after usePgTable has run at least once, though — for a cold-start route, pass the initial data through options.data instead.

TScharacters/route.ts
// Tanstack Router loader — prime the store before the route renders.
// usePgTable on the rendered route sees data is already there and skips the
// first fetch.
export const Route = createFileRoute('/characters')({
loader: async () => {
  const rows = await api.fetchCharacters()
  setTableData('characters', rows)               // store entry must exist first
  setTableState('characters', { loading: false })
},
component: CharactersPage,
})

// If the store entry doesn't exist yet (route landed cold), setTableData
// is a no-op. Use a one-shot mount inside the component, or guard with
// hasTable() and seed via the initial data option on usePgTable.

Toast handlers and undo

Toast callbacks are detached from the component that fired them — passing a ref through is awkward, and useEffect on the toast doesn't exist. getTableData + setTableData from the handler read and write the live store directly, addressed by pgTableId only.

TScharacters/toast-undo.ts
// A toast handler reads and writes the store without subscribing.
import type { PgTableData } from '@westopp/pgtable'
import type { charactersColumns } from './characters-table-config'

const onToastUndo = (rowsToRestore: PgTableData<typeof charactersColumns>) => {
const data = getTableData('characters') ?? []
setTableData('characters', [...data, ...rowsToRestore])
}

Websockets, pollers, listeners outside React

A long-lived subscription wired up at app boot can write rows into a table as they arrive. The table doesn't need to be mounted yet — if the entry exists, the write commits; if not, the write is a no-op and the table gets the next update when the user navigates to it.

TScharacters/websocket.ts
// A poller writes new rows in from a websocket — no component involved.
import type { PgTableRow } from '@westopp/pgtable'
import type { charactersColumns } from './characters-table-config'

ws.on('character.updated', (row: PgTableRow<typeof charactersColumns>) => {
setTableData('characters', (rows) => {
  const i = rows.findIndex((r) => r.id === row.id)
  if (i === -1) rows.push(row)
  else          rows[i] = row
})
})

ws.on('character.deleted', ({ id }: { id: string }) => {
setTableData('characters', (rows) => rows.filter((r) => r.id !== id))
})

The full API from outside

getTable returns the same PgTableApi object the hook hands you — including isDirtyData, dirtyStateProperties, lastUpdatedAt, getColumnsArray. If you can do it from React, you can do it from anywhere.

TScharacters/full-api.ts
// getTable returns the same PgTableApi the hook returns — every method is
// available, not just data/state writes.
const api = getTable<typeof charactersColumns, typeof charactersState>('characters')
if (!api) return

if (api.isDirtyState()) {
await save(api.dirtyStateProperties())
}

const sort         = api.getState('sort')
const sinceUpdate  = api.lastUpdatedAt()
const columnsArray = api.getColumnsArray()

Sign-out and reset

For app-level cleanup, walk the persisted entries and drop them. removeTable handles the in-memory side; clearStoredTables handles webstorage. dropTableEverywhere(id) is the all-in-one for a specific table.

TSauth/sign-out.ts
// "Sign out" — clear every persisted table from local storage, drop their
// in-memory entries too.
import { clearStoredTables, getPersistedTableProps, removeTable } from '@westopp/pgtable'

export const onSignOut = () => {
for (const { pgTableId } of getPersistedTableProps()) {
  removeTable(pgTableId)
}
clearStoredTables({ mode: 'local' })
clearStoredTables({ mode: 'session' })
}