Guides
Server-Side Data
Integrate with server-side sorting, filtering, and pagination
Server-Side Data
For very large datasets or when data must remain on the server, use server-side data operations.
When to Use Server-Side
- Dataset too large to load into browser memory
- Data requires real-time updates from server
- Complex filtering/sorting logic on server
- Security requirements to keep data server-side
Basic Setup
import { Grid, createServerDataSource } from "gp-grid-react";
const dataSource = createServerDataSource(async (request) => {
const response = await fetch("/api/grid-data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
return response.json();
});
function ServerGrid() {
return (
<Grid
columns={columns}
dataSource={dataSource}
rowHeight={36}
/>
);
}Request Format
The server receives a request object:
interface DataSourceRequest {
pagination: {
pageIndex: number; // Current page (0-indexed)
pageSize: number; // Rows per page
};
sort?: SortModel[]; // Sort configuration
filter?: FilterModel; // Filter configuration
}
// Example request
{
pagination: { pageIndex: 0, pageSize: 100 },
sort: [{ colId: "name", direction: "asc" }],
filter: {
salary: {
conditions: [{ type: "number", operator: ">", value: 50000 }],
combination: "and"
}
}
}Response Format
Return rows and total count:
interface DataSourceResponse<TData> {
rows: TData[]; // Current page of data
totalRows: number; // Total rows matching filters
}
// Example response
{
rows: [
{ id: 1, name: "Giovanni", salary: 75000 },
{ id: 2, name: "Luca", salary: 82000 },
// ...
],
totalRows: 10000
}Express.js Example
// server.ts
import express from "express";
app.post("/api/grid-data", async (req, res) => {
const { pagination, sort, filter } = req.body;
// Build database query
let query = db.select().from(employees);
// Apply filters
if (filter) {
for (const [field, model] of Object.entries(filter)) {
for (const condition of model.conditions) {
if (condition.type === "number") {
switch (condition.operator) {
case ">":
query = query.where(field, ">", condition.value);
break;
case "<":
query = query.where(field, "<", condition.value);
break;
// ... other operators
}
}
// ... handle other types
}
}
}
// Apply sorting
if (sort?.length) {
for (const s of sort) {
query = query.orderBy(s.colId, s.direction);
}
}
// Get total count
const totalRows = await query.clone().count();
// Apply pagination
const { pageIndex, pageSize } = pagination;
query = query.offset(pageIndex * pageSize).limit(pageSize);
const rows = await query;
res.json({ rows, totalRows });
});Handling Sort Models
// SortModel structure
type SortModel = {
colId: string;
direction: "asc" | "desc";
};
// Multiple columns (for multi-sort)
[
{ colId: "department", direction: "asc" },
{ colId: "salary", direction: "desc" }
]Handling Filter Models
// FilterModel structure
type FilterModel = Record<string, ColumnFilterModel>;
// ColumnFilterModel
{
conditions: FilterCondition[];
combination: "and" | "or";
}
// Example: salary > 50000 AND salary < 100000
{
salary: {
conditions: [
{ type: "number", operator: ">", value: 50000 },
{ type: "number", operator: "<", value: 100000 }
],
combination: "and"
}
}Error Handling
const dataSource = createServerDataSource(async (request) => {
try {
const response = await fetch("/api/grid-data", {
method: "POST",
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error("Failed to fetch grid data:", error);
// Return empty result on error
return { rows: [], totalRows: 0 };
}
});Caching Considerations
For better UX, consider caching pages:
const cache = new Map<string, DataSourceResponse>();
const dataSource = createServerDataSource(async (request) => {
const cacheKey = JSON.stringify(request);
if (cache.has(cacheKey)) {
return cache.get(cacheKey)!;
}
const result = await fetchFromServer(request);
cache.set(cacheKey, result);
return result;
});Clear cache when data changes on the server.