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;
}| Option | Default | Description |
|---|---|---|
getFieldValue | - | Custom accessor for nested properties |
getValueFormatter | - | Lookup for a field's valueFormatter so text filters compare against the displayed value |
useWorker | true | Use 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;
}| Member | Required | Description |
|---|---|---|
loadMode | No | "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. |
query | Yes | Async function returning { rows, totalRows } for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes. |
destroy | No | Cleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid. |
moveRow | No | Called 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
| Scenario | Recommended |
|---|---|
| Small dataset (under 10k rows) | rowData prop |
| Large dataset, client-side | createClientDataSource |
| Editable data with CRUD, inside a React component | useGridData hook |
| Editable data, outside a component (module, shared singleton) | createMutableClientDataSource |
| Server-side operations | createServerDataSource |