@tanstack/table-core
Version:
Headless UI for building powerful tables & datagrids for TS/JS.
165 lines (122 loc) • 4.12 kB
Markdown
name: client-vs-server
description: >
Choose client or server ownership for filtering, grouping, sorting, expanding, and pagination in TanStack Table v9. Load for manual* flags, mixed pipelines, server counts, or deciding which dataset each row-model stage receives.
metadata:
type: sub-skill
library: '@tanstack/table-core'
library_version: '9.0.0'
requires: ['core', 'table-features']
sources:
- 'TanStack/table:docs/guide/row-models.md'
- 'TanStack/table:packages/table-core/src/core/row-models/coreRowModelsFeature.utils.ts'
- 'TanStack/table:examples/react/with-tanstack-query'
This skill builds on `core` and `table-features`. Read them first for the model pipeline and plugin slots.
## Setup
```ts
import {
rowPaginationFeature,
rowSortingFeature,
tableFeatures,
} from '@tanstack/table-core'
export const features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
})
export const serverOptions = {
manualSorting: true,
manualPagination: true,
rowCount: 12_450,
} as const
```
## Core Patterns
### Client owns the complete pipeline
```ts
const features = tableFeatures({
columnFilteringFeature,
filteredRowModel: createFilteredRowModel(),
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
rowPaginationFeature,
paginatedRowModel: createPaginatedRowModel(),
})
```
Pass the full client dataset so each stage can process all rows.
### Server owns processing
```ts
const options = {
data: serverPage.rows,
manualFiltering: true,
manualSorting: true,
manualPagination: true,
rowCount: serverPage.total,
}
```
Put controlled filter, sorting, and pagination state into the request/query key.
## Common Mistakes
### [CRITICAL] Treating manual flags as requests
Wrong:
```ts
const options = { manualSorting: true, data: unsortedRows }
```
Correct:
```ts
const sortedPage = await fetchPage({ sorting })
const options = { manualSorting: true, data: sortedPage.rows }
```
`manualSorting` only bypasses client sorting; it never calls a backend.
Source: `packages/table-core/src/features/row-sorting/rowSortingFeature.types.ts`
### [HIGH] Expecting manual pagination to slice
Wrong:
```ts
const options = { data: allRows, manualPagination: true }
```
Correct:
```ts
const page = await fetchPage({ pageIndex, pageSize })
const options = {
data: page.rows,
manualPagination: true,
rowCount: page.rowCount,
}
```
Manual pagination assumes `data` already represents the intended page. Pass the stable page result directly. If the application deliberately performs custom local slicing instead, derive it with the framework's memo/computed primitive and keep the reference stable until its actual inputs change.
Source: `docs/framework/react/guide/pagination.md#manual-server-side-pagination`
### [HIGH] Sorting only the loaded page accidentally
Wrong:
```ts
const options = {
data: serverPage.rows,
manualPagination: true,
manualSorting: false,
}
```
Correct:
```ts
const options = {
data: serverPage.rows,
manualPagination: true,
manualSorting: true,
}
```
Client sorting can see only loaded rows, so it cannot produce database-wide order.
Source: `docs/guide/row-models.md`
### [HIGH] Recreating processed data or columns inline
Wrong:
```ts
const options = {
data: allRows.filter(matchesFilters).slice(pageStart, pageEnd),
columns: makeColumns(),
}
```
Correct:
```ts
const processedRows = pageResult.rows
const columns = sharedColumns
const options = { data: processedRows, columns }
```
`data` and `columns` are model inputs. Keep both references stable between meaningful changes with module constants, state, or the adapter's memo/computed primitive; otherwise Table repeatedly invalidates row and column models and some adapters can enter render loops. “Manual” describes processing ownership, not permission to derive new arrays inside table options.
Source: `docs/guide/data.md`
## API Discovery
Inspect `node_modules/@tanstack/table-core/dist/core/row-models/coreRowModelsFeature.utils.d.ts` for pipeline order and each feature's `.types.d.ts` for its `manual*` contract.