Grid The PretableSurface component
The PretableSurface component
The full React grid surface with declarative and explicit row-model ownership.
<PretableSurface> is the production grid component: indexed rendering,
selection, keyboard navigation, editing, clipboard, filters, sorting, grouping,
native date and number formatting, and custom presentation hooks.
Declarative rows mode
A live-events grid in the default rows mode — pass rows and columns, and PretableSurface reconciles later rows props into one long-lived local row model for you.
That's the whole recipe — typed columns, rows, and ariaLabel:
import { createColumnHelper } from "@pretable/core";
import { PretableSurface } from "@pretable/react";
interface Event {
id: string;
timestamp: string;
message: string;
}
const column = createColumnHelper<Event>();
const columns = [
column.accessor("timestamp", { type: "text", header: "Time" }),
column.accessor("message", { type: "text", header: "Message" }),
] as const;
export function EventGrid({ events }: { events: Event[] }) {
return (
<PretableSurface
ariaLabel="Live events"
rows={events}
columns={columns}
viewportHeight={520}
/>
);
}The surface creates one local row model for its lifetime and reconciles later rows props by stable ID. This is the default application path. Rows with a string or number id need no identity prop; pass getRowId for another stable key.
Explicit-model mode
Use explicit ownership for streaming or other imperative producers. Below, a chat grid owns its row model directly and connects it to a stream with connectElementStream — the same explicit-model shape, applied to @pretable/stream-adapter's highest-frequency case:
Turn a streaming LLM response into rows with connectElementStream and append them to the grid as they arrive.
The shape in general:
const rowModel = createLocalRowModel({ rows: [], columns });
const connection = connectElementStream(rowModel, stream);
<PretableSurface
ariaLabel="Streaming events"
model={rowModel}
viewportHeight={520}
/>;The modes are mutually exclusive: do not pass rows or getRowId with model. Optional columns in model mode may change presentation, not accessors, comparators, filters, aggregates, or IDs.
Configuration
| Area | Props |
|---|---|
| Geometry | viewportHeight, viewportStyle, overscan |
| Rendering | renderBodyCell, renderHeaderCell, row/cell/header class and prop callbacks |
| Value presentation | locale; column dateFormat, numberFormat, format, and formatAggregate |
| Controlled UI | state, onSelectionChange, onFocusChange |
| Row selection | rowSelectionColumn, state.rowSelection, onRowSelectionChange — its own slice |
| Rows-mode query | the exact query, onQueryChange pair |
| Editing | rows mode: onRowChange; model mode: beforeRowChange; shared: onPaste |
| Clipboard | onCopy, copyToClipboard, copyWithHeaders, messages |
| Grouping | groupPanel, groupColumn, hideGroupedColumns, initialExpansion |
| Tool panel | toolPanel — on by default; see Tool panel |
| Observation | onGridReady, onTelemetryChange |
Rows-mode edit callbacks are proposals: update your rows value and let the next prop reconcile. Explicit-model edits validate the batch and publish one row-model transaction.
Native dateFormat / numberFormat options and locale format compatible
cells, inherited group aggregates, built-in clipboard text, and CSV through one
precedence pipeline. format and formatAggregate remain the custom
overrides. Canonical date behavior is presentation-only here: raw values stay
YYYY-MM-DD | null, and external query authority is never reapplied locally.
See Date formatting and Number
formatting.
For common semantic presentations, PretableBadge, PretableDelta, PretableEntity, and PretableStatus provide theme-aware contrast, direction markers, primary/secondary text, and state dots without rebuilding cell behavior. See Cell presentations.
Controlled state and ownership
The rows-mode query/onQueryChange pair owns filters, sort, and grouping together. The state prop controls UI interaction and column-layout slices. In explicit-model mode, application code sends query and row mutations to rowModel; an onGridReady handle remains UI-only.
See Row grouping and aggregation for typed grouping, aggregate, expansion, and group-panel contracts.
Server-applied filtering and sorting
processing declares whether filter and sort work was performed by the local engine or an external authority; resultMeta then describes the result those rows came from, and dataState says where its request stands. Both are acted on in rows mode: the surface stops the engine applying query.filters and query.sort while continuing to publish them, so the funnel, onQueryChange, and aria-sort are unchanged. All of it — query ownership, totals, the lifecycle phases, and renderBodyState — is documented against a live endpoint in Server-side data.
Telemetry
Telemetry distinguishes source/logical rows from the currently rendered viewport range. Keep onTelemetryChange stable with useCallback; it may fire during hot rendering paths.
| Field | Type | Notes |
|---|---|---|
focusedRowId | TRowId | PretableGroupId | null | The focused row, the focused group row's id under grouping, or null when nothing is focused. |
loadedRowCount | number | Source rows loaded into the row model. |
renderedRowCount | number | Rows currently in the DOM — the viewport range plus overscan. |
rowModelRowCount | number | Visible rows the model holds after filtering, group rows included. |
selectedRowId | TRowId | null | Start row of the first selection range, or null. |
totalHeight | number | Scroll height of every row, in px. |
totalRowCount | number | Source rows before filtering — today the same number loadedRowCount reports. |
visibleRowCount | number | Rows intersecting the viewport, overscan excluded. |
visibleRowRange | { end: number; start: number } | Half-open row-index range of those rows; { start: 0, end: 0 } when none are visible. |
windowGap | { readonly direction: "before" | "after"; readonly rowCount: number } | Optional; the near-edge signal for windowed datasets. |
Streaming guidance
Use the rows prop for normal React updates. For high-frequency streams, own a row model and connect @pretable/stream-adapter to it. The adapter animation-frame batches transactions while the surface requests only its indexed viewport range.