Server-Side Data
Pass a dataSource instead of a data array to delegate sorting, filtering, and pagination to your server.
Live Demo
The demo above uses Radix UI for styling. To see this feature with the Fluent UI implementation, click "Open in online demo" below the demo.
Quick Example
- React
import { OGrid } from '@alaarab/ogrid-react-radix';
import type { IDataSource, IFetchParams, IPageResult } from '@alaarab/ogrid-react-radix';
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
const dataSource: IDataSource<Employee> = {
async fetchPage(params: IFetchParams): Promise<IPageResult<Employee>> {
const query = new URLSearchParams({
page: String(params.page),
pageSize: String(params.pageSize),
...(params.sort && {
sortField: params.sort.field,
sortDir: params.sort.direction,
}),
});
const res = await fetch(`/api/employees?${query}`);
return res.json(); // { items: Employee[], totalCount: number }
},
};
function App() {
return (
<OGrid
columns={columns}
dataSource={dataSource}
getRowId={(e) => e.id}
defaultPageSize={25}
/>
);
}
The OGrid component has the same props across all React UI packages. To switch, just change the import:
- Radix (lightweight, default):
from '@alaarab/ogrid-react-radix' - Fluent UI (Microsoft 365 / SPFx):
from '@alaarab/ogrid-react-fluent'- wrap in<FluentProvider>
The grid fetches the first page on mount and re-fetches whenever page, sort, or filters change.
Replacing the data source
A new dataSource object tells the grid you switched backends: it refetches, drops cached rows (windowed sources), and reloads filter options. Which new objects count as a switch:
- The same object, or a new object whose own properties are all identical to the previous one's (for example
dataSource={{ fetchPage }}around a stablefetchPage): not a switch, nothing refetches. - Any other new object: a switch. An inline object with inline functions is new on every render, so it costs one extra fetch the first time the parent re-renders.
- With
dataSourceKey: only a change of the key is a switch. The object can be rebuilt on every render, inline functions included; the grid always calls the latest one.
<OGrid
columns={columns}
getRowId={(e) => e.id}
// Rebuilt every render; refetches only when tenantId changes.
dataSource={{ fetchPage: (params) => api.employees(tenantId, params) }}
dataSourceKey={tenantId}
/>
To reload the same source (data changed on the server), call gridRef.current?.refreshData() instead.
IDataSource<T> Reference
interface IDataSource<T> {
fetchPage(params: IFetchParams): Promise<IPageResult<T>>;
fetchFilterOptions?(field: string): Promise<string[]>;
searchPeople?(query: string): Promise<UserLike[]>;
getUserByEmail?(email: string): Promise<UserLike | undefined>;
}
| Method | Required | Description |
|---|---|---|
fetchPage | Yes | Fetches a page of data. Called on page/sort/filter changes. |
fetchFilterOptions | No | Returns options for multiSelect filters with optionsSource: 'api'. |
searchPeople | No | Searches people for people-type column filters. |
getUserByEmail | No | Resolves a user by email (for restoring saved people filters). |
IFetchParams
interface IFetchParams {
page: number; // 1-indexed
pageSize: number;
sort?: { field: string; direction: 'asc' | 'desc' };
filters: IFilters; // See Types reference for FilterValue shapes
}
IPageResult<T>
interface IPageResult<T> {
items: T[];
totalCount: number; // drives pagination controls
}
Filter Options & People Search
const dataSource: IDataSource<Employee> = {
async fetchPage(params) { /* ... */ },
async fetchFilterOptions(field: string): Promise<string[]> {
const res = await fetch(`/api/employees/filter-options/${field}`);
return res.json(); // ['Engineering', 'Marketing', 'Sales']
},
async searchPeople(query: string): Promise<UserLike[]> {
const res = await fetch(`/api/people/search?q=${query}`);
return res.json();
},
async getUserByEmail(email: string): Promise<UserLike | undefined> {
const res = await fetch(`/api/people/${email}`);
if (!res.ok) return undefined;
return res.json();
},
};
Error Handling & Loading
<OGrid
columns={columns}
dataSource={dataSource}
getRowId={(e) => e.id}
onError={(error) => {
console.error('Grid fetch failed:', error);
showToast('Failed to load data.');
}}
/>
The grid manages loading state automatically. You can also control it via gridRef.current?.setLoading(true).
Client-Side vs. Server-Side
Client-side (data) | Server-side (dataSource) | |
|---|---|---|
| Data location | All rows in memory | Fetched per page |
| Sorting / Filtering | In-memory by the grid | Your server handles it |
| Best for | < 10,000 rows | Large datasets, real-time data |