gp-grid-logo
API Reference

Data Sources

Connect the React grid to client-side arrays or server-side endpoints, and plug streaming, paginated, or fully remote data flows into gp-grid with ease.

gp-grid provides a flexible data source abstraction for both client-side and server-side data handling.

Basic Usage with rowData

The simplest approach is using the rowData prop directly:

const data = [
  { id: 1, name: "Giovanni" },
  { id: 2, name: "Luca" },
];

<Grid columns={columns} rowData={data} rowHeight={36} />

This internally creates a client data source.

Client Data Source

For more control, use createClientDataSource:

import { Grid, createClientDataSource } from "@gp-grid/react";

const data = generateLargeDataset(1000000);
const dataSource = createClientDataSource(data);

<Grid columns={columns} dataSource={dataSource} rowHeight={36} />

Options

interface ClientDataSourceOptions<TData> {
  getFieldValue?: (row: TData, field: string) => CellValue;
  getValueFormatter?: (field: string) => ((v: CellValue) => string) | undefined;
  useWorker?: boolean;
  parallelSort?: ParallelSortOptions | false;
}
OptionDefaultDescription
getFieldValue-Custom accessor for nested properties
getValueFormatter-Lookup for a field's valueFormatter so text filters compare against the displayed value
useWorkertrueUse Web Worker for sorting large datasets
parallelSort-Options for parallel sorting (only used when useWorker is true)

Nested Properties

const data = [
  { id: 1, user: { name: "Giovanni", email: "g@example.com" } },
];

const dataSource = createClientDataSource(data, {
  getFieldValue: (row, field) => {
    if (field === "userName") return row.user.name;
    if (field === "userEmail") return row.user.email;
    return row[field as keyof typeof row];
  },
});

Mutable Data Source

For CRUD operations and real-time data updates, use the useGridData hook inside React components. It wraps createMutableClientDataSource with a stable reference across re-renders and exposes addRows / removeRows helpers bound to the current data source.

import { useGridData } from "@gp-grid/react";

function MyGrid() {
  const { dataSource, addRows, removeRows } = useGridData(initialData, {
    getRowId: (row) => row.id,  // Required: unique ID accessor
    debounceMs: 50,             // Batch updates (default: 50)
  });

  // Add rows
  addRows([{ id: 3, name: "Mario" }]);

  // Remove rows
  removeRows([1, 2]);

  // Update a cell
  dataSource.updateCell(3, "name", "Maria");

  // Update a row
  dataSource.updateRow(3, { name: "Maria", email: "maria@example.com" });

  // Force immediate processing
  await dataSource.flushTransactions();

  return <Grid columns={columns} dataSource={dataSource} rowHeight={36} />;
}

Reach for the raw createMutableClientDataSource factory only outside React components — module-level singletons, non-React code, or shared utilities. Inside a component, prefer useGridData: it handles subscription lifecycle, prevents stale closures, and keeps the dataSource reference stable across renders.

// Outside a component (e.g. a shared module)
import { createMutableClientDataSource } from "@gp-grid/react";

export const sharedDataSource = createMutableClientDataSource(seed, {
  getRowId: (row) => row.id,
});

Transaction System

The mutable data source uses an internal transaction system that provides several key benefits:

Automatic Batching - Multiple operations within the debounce window are combined into a single transaction, minimizing re-renders and improving performance.

Optimistic Processing - Changes are queued and processed asynchronously, allowing the UI to remain responsive during bulk operations.

Transaction Callbacks - Track when transactions are processed by subscribing to the data source:

const { dataSource } = useGridData(data, {
  getRowId: (row) => row.id,
  debounceMs: 50,
});

const unsubscribe = dataSource.subscribe((result) => {
  console.log(`Added: ${result.added}, Removed: ${result.removed}, Updated: ${result.updated}`);
  // Update external state, analytics, etc.
});

// Cleanup on unmount
useEffect(() => unsubscribe, []);

Or use the raw factory createMutableClientDataSource if you need a one-shot onTransactionProcessed callback:

const dataSource = createMutableClientDataSource(seed, {
  getRowId: (row) => row.id,
  onTransactionProcessed: (result) => {
    console.log(`Added: ${result.added}, Removed: ${result.removed}`);
  },
});

Flush Control - Force immediate processing when needed:

// Wait for all pending transactions
await dataSource.flushTransactions();

// Check if there are pending changes
if (dataSource.hasPendingTransactions()) {
  // Handle pending state
}

useGridData Hook Options

interface UseGridDataOptions<TData> {
  getRowId: (row: TData) => RowId;     // Required: unique ID accessor
  debounceMs?: number;                  // Batch window (default: 50)
  useWorker?: boolean;                  // Use Web Worker for sorting (default: true)
  parallelSort?: ParallelSortOptions | false;
}

createMutableClientDataSource Factory Options

Use these options when creating a mutable data source outside a React component:

interface MutableClientDataSourceOptions<TData> {
  getRowId: (row: TData) => RowId;     // Required: unique ID accessor
  getFieldValue?: (row: TData, field: string) => CellValue;
  getValueFormatter?: (field: string) => ((v: CellValue) => string) | undefined;
  debounceMs?: number;                  // Batch window (default: 50)
  onTransactionProcessed?: (result: TransactionResult) => void;
  useWorker?: boolean;                  // Use Web Worker for sorting (default: true)
  parallelSort?: ParallelSortOptions | false;
}

interface TransactionResult {
  added: number;    // Count of rows added
  removed: number;  // Count of rows removed
  updated: number;  // Count of rows updated
}

MutableDataSource Interface

interface MutableDataSource<TData> extends DataSource<TData> {
  addRows(rows: TData[]): void;
  removeRows(ids: RowId[]): void;
  updateCell(id: RowId, field: string, value: CellValue): void;
  updateRow(id: RowId, data: Partial<TData>): void;
  flushTransactions(): Promise<void>;
  hasPendingTransactions(): boolean;
  getDistinctValues(field: string): CellValue[];
  getRowById(id: RowId): TData | undefined;
  getTotalRowCount(): number;
  subscribe(listener: DataChangeListener): () => void;
  clear(): void;
  moveRow(fromIndex: number, toIndex: number): void;
}

Use Cases

The mutable data source is ideal for:

  • Real-time dashboards - Live data feeds with frequent updates
  • Streaming data - WebSocket or SSE data streams
  • User editing - Forms and inline editing with immediate feedback
  • Bulk operations - Import, batch updates, mass delete

See the Live Data example for a complete implementation.

DataSource Interface

All data sources implement the DataSource<TData> interface. Both createClientDataSource and createServerDataSource return objects that conform to it; MutableDataSource<TData> extends it with mutation methods.

interface DataSource<TData> {
  readonly loadMode?: "all" | "paginated";
  query(request: DataSourceRequest): Promise<DataSourceResponse<TData>>;
  destroy?(): void;
  moveRow?(fromIndex: number, toIndex: number): void;
}
MemberRequiredDescription
loadModeNo"all" (default for client data sources) or "paginated" (default for server). Controls whether the grid requests every row at once or only the visible window via range.startRow / range.endRow.
queryYesAsync function returning { rows, totalRows } for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes.
destroyNoCleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid.
moveRowNoCalled on row drag-end. Implement it to update the underlying data; the grid does not mutate data on its own.

Server Data Source

For server-side sorting, filtering, and windowed row loading:

import { createServerDataSource } from "@gp-grid/react";

const dataSource = createServerDataSource(async (request) => {
  const { range, sort, filter } = request;

  const response = await fetch("/api/data", {
    method: "POST",
    body: JSON.stringify({
      startRow: range.startRow,
      endRow: range.endRow,
      sortBy: sort,
      filters: filter,
    }),
  });

  const result = await response.json();

  return {
    rows: result.data,
    totalRows: result.total,
  };
});

<Grid columns={columns} dataSource={dataSource} rowHeight={36} />

Pass { loadMode: "all" } as a second argument if the server returns the entire result set in one call. The default is "paginated" — the grid only requests the row window currently in view via range.startRow / range.endRow.

const dataSource = createServerDataSource(queryFn, { loadMode: "paginated" });

Request Interface

interface DataSourceRequest {
  range: {
    startRow: number; // First row index to fetch (0-indexed, inclusive)
    endRow: number;   // First row index after the range (exclusive)
  };
  sort?: SortModel[];
  filter?: FilterModel;
}

Response Interface

interface DataSourceResponse<TData> {
  rows: TData[];
  totalRows: number;
}

Choosing a Data Source

ScenarioRecommended
Small dataset (under 10k rows)rowData prop
Large dataset, client-sidecreateClientDataSource
Editable data with CRUD, inside a React componentuseGridData hook
Editable data, outside a component (module, shared singleton)createMutableClientDataSource
Server-side operationscreateServerDataSource

On this page