Browse documentation
Agent docs/table/server-grid-state
contract

Coordinate sorting and pagination for a server-backed table

Let Table own view state and Query own remote data, using manualSorting, manualPagination, and rowCount to avoid processing only the current page.

View raw Markdown
CONTRACT

Execution contract

Use when
Use when a table receives one server page and sorting and pagination must flow through URL state, query keys, and the API request.
Avoid when
Prefer built-in sorting and pagination row models when the complete, reasonably small dataset is already in the client.
Preconditions
Define sortable-column allowlists, zero-based pageIndex, pageSize bounds, total rowCount, a stable query key, and one owner for URL and Table state.
Verification
Verify sort, pagination, refresh, back navigation, and empty pages; each state change issues one correct request and rowCount disables the final next-page action.
Failure mode
Sorting only the current page indicates an accidental getSortedRowModel; frozen controls usually mean an onSortingChange or onPaginationChange callback lacks its matching state value.
Security
Column ids, sort direction, page, and pageSize are external input; map columns through an allowlist, bound values, authorize independently, and never interpolate column ids into SQL.
01

Assign state ownership first

Table owns the sorting and pagination view model, the Query key identifies the remote result, and the API sorts and paginates the full dataset. If state must be shareable and navigable, let Router search params become the single upstream owner instead of duplicating drift-prone state in both the URL and useState.

02

Controlled state must be wired in pairs

Every onXChange callback needs its matching state.x value or that state freezes. Server mode omits client sorting and pagination row models; manualSorting and manualPagination state that incoming data is already processed. rowCount lets Table derive page count from pageSize.

src/features/issues/useIssueGrid.tstsx
import { useQuery } from '@tanstack/react-query'
import {
  getCoreRowModel, type PaginationState, type SortingState, useReactTable,
} from '@tanstack/react-table'
import { useState } from 'react'

const emptyIssues: Issue[] = []

export function useIssueGrid() {
  const [sorting, setSorting] = useState<SortingState>([])
  const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 20 })
  const result = useQuery({
    queryKey: ['issues', 'grid', { sorting, pagination }] as const,
    queryFn: () => fetchIssuePage({ sorting, pagination }),
  })
  const table = useReactTable({
    columns: issueColumns, data: result.data?.items ?? emptyIssues,
    getCoreRowModel: getCoreRowModel(), getRowId: (issue) => issue.id,
    manualSorting: true, manualPagination: true, rowCount: result.data?.rowCount ?? 0,
    state: { sorting, pagination }, onSortingChange: setSorting,
    onPaginationChange: setPagination,
  })
  return { result, table }
}
03

Keep cross-page rules consistent

Do not mix server pagination with getSortedRowModel or only the current page will sort. Reset to the first page when sorting or filters change; if the URL owns state, update sorting and pageIndex in one navigation. Query keys must contain every response-shaping value without unrelated objects that change every render.

04

Map column ids instead of executing them

Client column ids are external strings. The server maps allowed ids to fixed database fields, bounds pageSize and pageIndex, rejects unknown sort directions, and authorizes before querying. rowCount must use the same authorization scope.

PRIMARY SOURCEShttps://tanstack.com/table/latest/docs/framework/react/guide/table-statehttps://tanstack.com/table/latest/docs/guide/sortinghttps://tanstack.com/table/latest/docs/guide/pagination
TanStack Atlas

Original bilingual knowledge · verified against primary sources

Friend linksGitHub