File: pagination-controlled.md | Updated: 11/15/2025
Search...
+ K
Auto
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples GitHub Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Docs Examples Github Contributors
Maintainers Partners Support Learn StatsBETA Discord Merch Blog GitHub Ethos Brand Guide
Documentation
Framework
React
Version
Latest
Search...
+ K
Menu
Getting Started
Core Guides
Feature Guides
Core APIs
Feature APIs
Enterprise
Examples
Framework
React
Version
Latest
Menu
Getting Started
Core Guides
Feature Guides
Core APIs
Feature APIs
Enterprise
Examples
React Example: Pagination Controlled
===========================================================================================================================================================================================================================================================================================================================================================================================================================================================
Code ExplorerCode
Interactive SandboxSandbox
src
fetchData.ts
index.css
main.tsx
.gitignore
README.md
index.html
package.json
tsconfig.json
vite.config.js
tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
keepPreviousData,
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
import './index.css'
import {
PaginationState,
useReactTable,
getCoreRowModel,
ColumnDef,
flexRender,
} from '@tanstack/react-table'
//
import { fetchData, Person } from './fetchData'
const queryClient = new QueryClient()
function App() {
const rerender = React.useReducer(() => ({}), {})[1]
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
{\
header: 'Name',\
footer: props => props.column.id,\
columns: [\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
footer: props => props.column.id,\
},\
{\
accessorFn: row => row.lastName,\
id: 'lastName',\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
footer: props => props.column.id,\
},\
],\
},\
{\
header: 'Info',\
footer: props => props.column.id,\
columns: [\
{\
accessorKey: 'age',\
header: () => 'Age',\
footer: props => props.column.id,\
},\
{\
header: 'More Info',\
columns: [\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
footer: props => props.column.id,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
footer: props => props.column.id,\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
footer: props => props.column.id,\
},\
],\
},\
],\
},\
],
[]
)
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const dataQuery = useQuery({
queryKey: ['data', pagination],
queryFn: () => fetchData(pagination),
placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page
})
const defaultData = React.useMemo(() => [], [])
const table = useReactTable({
data: dataQuery.data?.rows ?? defaultData,
columns,
// pageCount: dataQuery.data?.pageCount ?? -1, //you can now pass in `rowCount` instead of pageCount and `pageCount` will be calculated internally (new in v8.13.0)
rowCount: dataQuery.data?.rowCount, // new in v8.13.0 - alternatively, just pass in `pageCount` directly
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true, //we're doing manual "server-side" pagination
// getPaginationRowModel: getPaginationRowModel(), // If only doing manual pagination, you don't need this
debugTable: true,
})
return (
<div className="p-2">
<div className="h-2" />
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => {
return (
<tr key={row.id}>
{row.getVisibleCells().map(cell => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
<div className="h-2" />
<div className="flex items-center gap-2">
<button
className="border rounded p-1"
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
className="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
className="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
className="border rounded p-1"
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage()}
>
{'>>'}
</button>
<span className="flex items-center gap-1">
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount().toLocaleString()}
</strong>
</span>
<span className="flex items-center gap-1">
| Go to page:
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
className="border p-1 rounded w-16"
/>
</span>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
{dataQuery.isFetching ? 'Loading...' : null}
</div>
<div>
Showing {table.getRowModel().rows.length.toLocaleString()} of{' '}
{dataQuery.data?.rowCount.toLocaleString()} Rows
</div>
<div>
<button onClick={() => rerender()}>Force Rerender</button>
</div>
<pre>{JSON.stringify(pagination, null, 2)}</pre>
</div>
)
}
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
)
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
keepPreviousData,
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
import './index.css'
import {
PaginationState,
useReactTable,
getCoreRowModel,
ColumnDef,
flexRender,
} from '@tanstack/react-table'
//
import { fetchData, Person } from './fetchData'
const queryClient = new QueryClient()
function App() {
const rerender = React.useReducer(() => ({}), {})[1]
const columns = React.useMemo<ColumnDef<Person>[]>(
() => [\
{\
header: 'Name',\
footer: props => props.column.id,\
columns: [\
{\
accessorKey: 'firstName',\
cell: info => info.getValue(),\
footer: props => props.column.id,\
},\
{\
accessorFn: row => row.lastName,\
id: 'lastName',\
cell: info => info.getValue(),\
header: () => <span>Last Name</span>,\
footer: props => props.column.id,\
},\
],\
},\
{\
header: 'Info',\
footer: props => props.column.id,\
columns: [\
{\
accessorKey: 'age',\
header: () => 'Age',\
footer: props => props.column.id,\
},\
{\
header: 'More Info',\
columns: [\
{\
accessorKey: 'visits',\
header: () => <span>Visits</span>,\
footer: props => props.column.id,\
},\
{\
accessorKey: 'status',\
header: 'Status',\
footer: props => props.column.id,\
},\
{\
accessorKey: 'progress',\
header: 'Profile Progress',\
footer: props => props.column.id,\
},\
],\
},\
],\
},\
],
[]
)
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const dataQuery = useQuery({
queryKey: ['data', pagination],
queryFn: () => fetchData(pagination),
placeholderData: keepPreviousData, // don't have 0 rows flash while changing pages/loading next page
})
const defaultData = React.useMemo(() => [], [])
const table = useReactTable({
data: dataQuery.data?.rows ?? defaultData,
columns,
// pageCount: dataQuery.data?.pageCount ?? -1, //you can now pass in `rowCount` instead of pageCount and `pageCount` will be calculated internally (new in v8.13.0)
rowCount: dataQuery.data?.rowCount, // new in v8.13.0 - alternatively, just pass in `pageCount` directly
state: {
pagination,
},
onPaginationChange: setPagination,
getCoreRowModel: getCoreRowModel(),
manualPagination: true, //we're doing manual "server-side" pagination
// getPaginationRowModel: getPaginationRowModel(), // If only doing manual pagination, you don't need this
debugTable: true,
})
return (
<div className="p-2">
<div className="h-2" />
<table>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => {
return (
<tr key={row.id}>
{row.getVisibleCells().map(cell => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
<div className="h-2" />
<div className="flex items-center gap-2">
<button
className="border rounded p-1"
onClick={() => table.firstPage()}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
className="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
className="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
className="border rounded p-1"
onClick={() => table.lastPage()}
disabled={!table.getCanNextPage()}
>
{'>>'}
</button>
<span className="flex items-center gap-1">
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of{' '}
{table.getPageCount().toLocaleString()}
</strong>
</span>
<span className="flex items-center gap-1">
| Go to page:
<input
type="number"
min="1"
max={table.getPageCount()}
defaultValue={table.getState().pagination.pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
table.setPageIndex(page)
}}
className="border p-1 rounded w-16"
/>
</span>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
{dataQuery.isFetching ? 'Loading...' : null}
</div>
<div>
Showing {table.getRowModel().rows.length.toLocaleString()} of{' '}
{dataQuery.data?.rowCount.toLocaleString()} Rows
</div>
<div>
<button onClick={() => rerender()}>Force Rerender</button>
</div>
<pre>{JSON.stringify(pagination, null, 2)}</pre>
</div>
)
}
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
)
