Client and server
The same table and the same props, with the work done in the browser, on your API, or split feature by feature.
Basics
Loading example…
A table in client mode holds all the rows and computes everything itself. A table in server mode holds one page and asks the API. Both are the same component with the same props; what changes is who does the work.
<DataTable data={people} columns={columns} getRowId={(p) => p.id} />
<DataTable dataMode="server" dataSource={dataSource} columns={columns} getRowId={(p) => p.id} />
dataMode="server" switches pagination, sorting, filtering, search, grouping and faceting to the
server at once. Expansion stays local, because sub-rows can still come from the data you already
have.
The query
Whichever mode you are in, the table maintains one normalized query: page, sort list, search term,
column filters and grouping. That object is what a server mode table sends, and you can read it
at any time with table.getQuery().
onQueryChange(query, change) fires once, after debouncing, whenever something that affects
server data changes, and it tells you why it changed. It is the single place to hang a fetch,
instead of six callbacks racing each other.
<DataTable
dataMode="server"
onQueryChange={(query) => void refetch(query)}
data={page.items}
rowCount={page.total}
/>
The per-slice callbacks (onSortingChange, onPaginationChange and the rest) still fire
immediately, for controlled state.
A data source instead of an effect
dataSource packages the fetch, so the table drives the request itself and gets loading, error,
refetch and facet handling for free:
interface PeoplePage {
content: Person[];
totalElements: number;
}
const dataSource = createRestDataSource<Person, PeoplePage>({
url: '/api/people',
mapQuery: (query) => ({
page: query.pagination.pageIndex,
size: query.pagination.pageSize,
sort: query.sorting.map((s) => s.id + ',' + (s.desc ? 'desc' : 'asc')),
q: query.globalFilter,
}),
mapResult: (body) => ({ rows: body.content, rowCount: body.totalElements }),
});
mapQuery turns the table's query into the request, and mapResult turns the response into
rows and a total. The second type argument is the response's shape, which is what lets
mapResult read it.
keepPreviousData keeps the old page visible while the next one loads, dedupeMs collapses
duplicate requests, and refetchInterval and refetchOnWindowFocus cover live data.
Mixing the two
Loading example…
Per-feature modes override dataMode, so you can page and filter on the server while sorting the
loaded page in the browser:
<DataTable dataMode="server" sortingMode="client" acknowledgePageLocalSorting />
That combination sorts the current page only, which is sometimes exactly what you want and sometimes a bug. The table warns about it in development until you acknowledge it in the props, which is also the documentation of the decision for whoever reads the code next.
Customization
createLocalDataSource gives an in-memory array the same interface as a REST source, which makes
tests and prototypes behave like production. useDataSource exposes the same machinery outside a
table, and useDataTable gives you the whole engine with no markup.
Limitations
- Server grouping with client pagination is not supported and throws in development: the table cannot slice group rows it did not build.
- Client faceting under server pagination describes the loaded page, not the result set. Use
fetchFacets; see Faceting. - A server that does not return a total cannot produce page numbers. Pass
rowCount: -1and the table showsof manyand switches to cursor-style navigation.
API
DataTableProps:dataMode,dataSource,paginationMode,sortingMode,filterMode,searchMode,groupingMode,facetingMode, themanual*flags,onQueryChange,rowCount,pageCount,keepPreviousData,dedupeMs,fetchOnMount,refetchInterval,refetchOnWindowFocus,acknowledgePageLocalSorting,acknowledgePageLocalFiltering.TableState:TableQueryand every state slice.TableInstance:getQuery,flushQuery,refresh,invalidate,getDataStatus.- Utilities:
createRestDataSource,createLocalDataSource.