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

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.

.md

That's the whole recipe — typed columns, rows, and ariaLabel:

EventGrid.tsx
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:

Streaming chat grid

Turn a streaming LLM response into rows with connectElementStream and append them to the grid as they arrive.

.md

The shape in general:

tsx
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

AreaProps
GeometryviewportHeight, viewportStyle, overscan
RenderingrenderBodyCell, renderHeaderCell, row/cell/header class and prop callbacks
Value presentationlocale; column dateFormat, numberFormat, format, and formatAggregate
Controlled UIstate, onSelectionChange, onFocusChange
Row selectionrowSelectionColumn, state.rowSelection, onRowSelectionChange — its own slice
Rows-mode querythe exact query, onQueryChange pair
Editingrows mode: onRowChange; model mode: beforeRowChange; shared: onPaste
ClipboardonCopy, copyToClipboard, copyWithHeaders, messages
GroupinggroupPanel, groupColumn, hideGroupedColumns, initialExpansion
Tool paneltoolPanel — on by default; see Tool panel
ObservationonGridReady, 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.

FieldTypeNotes
focusedRowIdTRowId | PretableGroupId | nullThe focused row, the focused group row's id under grouping, or null when nothing is focused.
loadedRowCountnumberSource rows loaded into the row model.
renderedRowCountnumberRows currently in the DOM — the viewport range plus overscan.
rowModelRowCountnumberVisible rows the model holds after filtering, group rows included.
selectedRowIdTRowId | nullStart row of the first selection range, or null.
totalHeightnumberScroll height of every row, in px.
totalRowCountnumberSource rows before filtering — today the same number loadedRowCount reports.
visibleRowCountnumberRows 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.

See also