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 |
Column Definition
Configure each React grid column individually with type, width, sorting, filtering, rendering, and editing options through the column definition object.
Events
Reference for grid instructions, lifecycle hooks, and DOM-style events emitted by the React Grid component for selection, sorting, and editing flows today.