Skip to main content

Server-Side Data

Pass a dataSource instead of a data array to delegate sorting, filtering, and pagination to your server.

Live Demo​

Simulated 300ms server latency - watch the loading state
Live
Try it in your framework

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​

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}
/>
);
}
Switching UI libraries

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 stable fetchPage): 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>;
}
MethodRequiredDescription
fetchPageYesFetches a page of data. Called on page/sort/filter changes.
fetchFilterOptionsNoReturns options for multiSelect filters with optionsSource: 'api'.
searchPeopleNoSearches people for people-type column filters.
getUserByEmailNoResolves 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
}
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 locationAll rows in memoryFetched per page
Sorting / FilteringIn-memory by the gridYour server handles it
Best for< 10,000 rowsLarge datasets, real-time data
  • Filtering -- filter types and configuration
  • Grid API -- setFilterModel() for programmatic filtering
  • Types -- IFilters, FilterValue, UserLike shapes