Skip to content
quartiledocsv0.1
GitHub

DataExplorer

PreviewIn-memoryControlled or uncontrolled

Inspect records with typed filters, multi-sort, grouping, saved views, controlled edits and CSV export.

Explore and edit real rows

Search, add a filter, reorder or pin columns, and export the matching records. Editable cells use a typed editor; this example commits changes to React state. Reload restores the original fixture.

Material samples
18 original fixture rows. Edits stay in this example until reload. Yield is stored as a fraction.
18 of 18 source rows
sample-01
sample-02
sample-03
sample-04
sample-05
1–5 of 18 rows

DataExplorer composes DataTable with a complete exploration toolbar. Use DataTable directly when the application supplies its own controls. The dataset explorer adds profiles, linked charts, file/URL snapshot loading and application-owned local view persistence. See Bring your data for source limits and reset behavior.

'use client';

import { useState } from 'react';
import { DataExplorer, dataset, createTableViewState,
  type DataTableColumn } from '@quartile/react';

const columns: DataTableColumn[] = [
  { field: 'id', width: 150, pinned: 'left' },
  { field: 'material', width: 140, editable: true },
  { field: 'yield', width: 140, editable: { type: 'number', nullable: true } },
];

export function SampleTable() {
  const [rows, setRows] = useState([
    { id: 'S-01', material: 'Ceramic', yield: 0.91 },
    { id: 'S-02', material: 'Alloy', yield: null as number | null },
  ]);
  const [view, setView] = useState(() => createTableViewState({ pageSize: 25 }));
  return <DataExplorer data={dataset(rows, { yield: { type: 'quantitative', format: 'percent' } })}
    rowKey="id" columns={columns} view={view} onViewChange={setView}
    exportFileName="samples.csv"
    onCellEdit={({ row: edited, field, value }) => {
      setRows((previous) => previous.map((row) =>
        row.id === edited.id ? { ...row, [field]: value } : row));
    }} />;
}

Know which rows a view describes

Shared Selection filters run first, excluding the table's own publisher. Local search and typed filters then narrow those rows, followed by grouping, stable multi-sort and pagination. Search includes configured fields even when their columns are hidden. All local filters combine with AND.

A table's local search and filters do not update sibling charts or KPIs. Row selection can publish a shared field when select is enabled. Label the population behind each metric, or derive a common filtered dataset when the whole workspace should follow the same local state.

Grouping uses one field and each column's configured aggregation. Edits are disabled for aggregate groups. CSV export includes every matching, sorted result row before pagination and only visible columns; grouped views export groups. It does not mean every original source record.

Typed filter semantics

KindOperationsMeaning
textcontains / notContains / eq / startsWithCase-insensitive text matching.
numbereq / neq / gt / gte / lt / lte / betweenFinite numbers; inclusive range with nullable open ends.
dateon / before / after / betweenYYYY-MM-DD UTC calendar days; between includes both days.
categoryin / notInTyped scalar identity; null can be selected explicitly.
booleanisStrict true or false.
emptyisEmpty / isNotEmptyNull, undefined, empty string or nonfinite number.

Date filters use UTC days: before excludes the selected day, after begins at the next day, and between ends just before the next day after its upper bound. They differ from the local-date convention of synchronous chart range predicates. Local category filters and grouping preserve typed identities. The legacy shared selection path can still equate nominal strings that parse to the same date; use stable non-date-like IDs for linked record selection. The category picker is bounded to 1,000 distinct source options, with saved selections retained.

PropTypeDefaultDescription
fieldrequiredstring——
typerequired'text' | 'number' | 'date' | 'category' | 'boolean' | 'empty'——
oprequired'contains' | 'notContains' | 'eq' | 'startsWith' | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'on' | 'before' | 'after' | 'in' | 'notIn' | 'is' | 'isEmpty' | 'isNotEmpty'——
valuestring | number | [number | null, number | null] | [string | null, string | null] | TableScalar[] | boolean——

Save view configuration

view and onViewChange make all controls application-owned. Otherwise defaultView initializes internal state. The versioned state contains search, filters, sort priorities, hidden/order/width/pin preferences, one grouping field and page settings.

import { parseTableViewState, serializeTableViewState } from '@quartile/react';

// In a client event handler; choose your own storage key and access scope.
localStorage.setItem('samples-view-v1', serializeTableViewState(view));
const saved = localStorage.getItem('samples-view-v1');
if (saved) setView(parseTableViewState(saved)); // Catch invalid/stale configurations in your UI.

The View control copies and loads JSON; it does not choose persistence or synchronization for you. Saved state contains neither source records nor shared selection. Validate stored input, keep stable column keys, and handle fields removed by a dataset revision. Changing local filters, sort or grouping resets paging.

PropTypeDefaultDescription
versionrequired1——
searchrequiredstring——
filtersrequiredTableFilter[]——
sortsrequiredTableSort[]——
hiddenColumnsrequiredstring[]——
columnOrderrequiredstring[]——
columnWidthsrequiredRecord<string, number>——
pinnedColumnsrequired{ left: string[]; right: string[] }——
groupByrequiredstring | null——
pagerequirednumber—Zero-based page.
pageSizerequirednumber——

Controlled editing and export

Editing requires a stable rowKey, onCellEdit and an editable column. The callback receives the original row, field, column key, old and proposed values. Its rowKey is an opaque typed identity when you supply a field name; use the original row's ID to update source records, or provide a rowKey function with your chosen representation. The callback may return a promise: the editor shows pending state and a rejected commit's error. The library never mutates your data or writes a database.

Editors support text, finite number, ISO date, boolean and a typed select. Add validate(value, row) for application rules. Nullable editors can commit null; the boolean editor exposes an Empty choice when nullable. The example stores yield as a fraction even though its display is a percentage.

Pinned columns need explicit numeric widths. Keyboard controls reorder and resize columns; this is not an Excel-style grid with range paste, formulas or collaborative writes.

onExport overrides browser download and receives CSV, rows, columns and view. Raw values are the default; choose csvFormat="formatted" to apply column/field formats and provider locale/time zone. The lower-level tableToCSV accepts mode, schema, locale and timeZone options. It escapes quotes, uses CRLF and prefixes formula-like strings. Formatted export does not reproduce cell JSX, bar-share/sparkline visuals or delta-pill rendering, and onExport.rows still contains result values. See export formatting.

PropTypeDefaultDescription
type'text' | 'number' | 'date' | 'boolean' | 'select'—Defaults to the field's schema type. Dates edit a UTC calendar day as YYYY-MM-DD.
optionsreadonly { value: TableScalar; label: string }[]——
nullableboolean——
validate(value: TableScalar, row: Row) => string | null | undefined—Return an error message to keep the editor open without publishing an edit.
PropTypeDefaultDescription
rowrequiredRow——
rowKeyrequiredstring—Stable identity produced by the DataTable rowKey prop.
fieldrequiredstring——
columnKeyrequiredstring——
previousValuerequiredunknown——
valuerequiredTableScalar——
PropTypeDefaultDescription
csvrequiredstring——
rowsrequiredreadonly Row[]—All matching, sorted result rows, before pagination. Groups when grouping is enabled.
columnsrequiredreadonly DataTableColumn[]——
viewrequiredTableViewState——

API reference and limits

PropTypeDefaultDescription
datarequiredDataInput<R>——
columnsrequiredDataTableColumn[]——
rowKeyrequiredNonNullable<DataTableProps<R>['rowKey']>——
viewTableViewState——
defaultViewPartial<TableViewState>——
onViewChange(view: TableViewState) => void——
exportFileNamestring'rows.csv'Defaults to rows.csv. Export includes all filtered rows and visible columns, not only this page.
csvFormat'raw' | 'formatted''raw'Raw cell values by default. Formatted uses field/column formats, not custom cell JSX.
onExport(result: DataExplorerExport) => void—Overrides the browser download, useful for controlled export destinations.
aria-labelstring——
classNamestring——
idstring—Publisher id.
styleCSSProperties——
titleReactNode——
heightnumber—Scroller height in px when virtualized (or to cap the table). Defaults to 480 when virtualized.
multipleboolean—Select several rows (default) or one.
footerReactNode—Right side of the footer.
typedSelectionboolean—Preserve physical field identities for prepared query results (including nominal dates).
selectstring—Field published to the selection when a row is clicked or Enter is pressed; selected rows highlight with a check. The table is not filtered by its own selection.
rankboolean—Adds a leading rank column (1, 2, 3 … after sorting).
captionReactNode—Secondary line under the title.
toolbarReactNode—Controls on the right of the title row.
nounstring—Plural noun for the page label, e.g. "products" in "1–8 of 12 products".
virtualizenumber | false—Row count above which only the visible rows render inside a fixed-height scroller. Defaults to 200; false turns it off. Ignored when paginating.
selectionstring | false—Selection to read from and publish to. Defaults to the nearest; false opts out.
onRowClick(row: Row) => void——
onCellEdit(edit: DataTableEdit) => void | Promise<void>—Controlled write: update the caller's data after accepting this edit. Rejections remain visible.

Also accepts the props of Omit< DataTableProps<R>, | 'data' | 'columns' | 'rowKey' | 'transform' | 'groupBy' | 'sort' | 'defaultSort' | 'onSortChange' | 'sorts' | 'defaultSorts' | 'onSortsChange' | 'manualSort' | 'page' | 'defaultPage' | 'onPageChange' | 'pageSize' | 'limit' >, spread onto the root element.

Frame and state props shared with the charts (5) · ChartStateProps
PropTypeDefaultDescription
loadingboolean | string—Shows the loading state at the chart's size, so the layout never jumps.
empty{ title?: string; description?: string; action?: ReactNode }—Title and description for the empty state. Shown when there are no rows.
errorboolean | string | Error | null—Shows the error state. A string or Error becomes the message.
errorCodestring—Shown in the error state; copyable for support.
onRetry() => void——

Filtering, grouping, sorting and CSV generation run synchronously over in-memory rows. Pagination bounds rendered rows, not computation. For worker-backed pages use QueryDataTable; DataExplorer is not automatically a remote query or permission boundary. Read the public table guide for the headless helpers and full contracts.

Report an issueBuilt Oct 21, 2026