Pagination
Add pagination to render a built-in pagination bar below the table.
All state is managed internally. No extra props needed for basic usage.
Built-in pagination
30 rows. Use the buttons above to change the initial rows-per-page, or use the built-in selector inside the table.
import { useState } from 'react';
import DataTable, { type TableColumn } from 'react-data-table-component';
const data = Array.from({ length: 30 }, (_, i) => ({
id: i + 1,
name: ['Aria Chen','Marcus Webb','Priya Kapoor','Jordan Ellis','Sam Rivera',
'Taylor Brooks','Casey Morgan','Alex Kim','Morgan Lee','Drew Park'][i % 10],
department: ['Engineering','Product','Design','Analytics','Sales','HR'][i % 6],
salary: 80000 + i * 3700,
status: ['Active','Remote','On Leave','Contractor'][i % 4],
}));
const columns: TableColumn<typeof data[0]>[] = [
{ name: 'Name', selector: r => r.name, sortable: true },
{ name: 'Department', selector: r => r.department, sortable: true },
{ name: 'Salary', selector: r => r.salary, sortable: true,
format: r => `$${r.salary.toLocaleString()}`, right: true },
{ name: 'Status', selector: r => r.status },
];
export default function App() {
const [perPage, setPerPage] = useState(5);
return (
<div>
<div>
{[5, 10, 15].map(n => (
<button key={n} onClick={() => setPerPage(n)}>{n}</button>
))}
</div>
{/* key remounts the table so paginationPerPage takes effect */}
<DataTable
key={perPage}
columns={columns}
data={data}
pagination
paginationPerPage={perPage}
paginationRowsPerPageOptions={[5, 10, 15, 30]}
highlightOnHover
striped
/>
</div>
);
} Server-side pagination
When your dataset is too large to load at once, delegate pagination to the server.
Set paginationServer and provide paginationTotalRows so the
pagination bar knows how many pages exist. Respond to onChangePage and
onChangeRowsPerPage to fetch each page.
Server-side pagination
100-row simulated dataset with 300 ms fake network latency. Change pages or the rows-per-page selector to trigger a new fetch.
import { useState, useEffect, useCallback } from 'react';
import DataTable, { type TableColumn } from 'react-data-table-component';
interface Employee { id: number; name: string; department: string; salary: number; status: string; }
const columns: TableColumn<Employee>[] = [
{ name: 'Name', selector: r => r.name },
{ name: 'Department', selector: r => r.department },
{ name: 'Salary', selector: r => r.salary, right: true,
format: r => `$${r.salary.toLocaleString()}` },
{ name: 'Status', selector: r => r.status },
];
export default function App() {
const [data, setData] = useState<Employee[]>([]);
const [loading, setLoading] = useState(true);
const [totalRows, setTotal] = useState(0);
const [perPage, setPerPage] = useState(10);
const load = useCallback(async (page: number, pp: number) => {
setLoading(true);
const res = await fetch(`/api/employees?page=${page}&limit=${pp}`);
const result = await res.json();
setData(result.rows);
setTotal(result.total);
setLoading(false);
}, []);
useEffect(() => { load(1, perPage); }, []);
return (
<DataTable
columns={columns}
data={data}
progressPending={loading}
pagination
paginationServer
paginationTotalRows={totalRows}
onChangePage={page => load(page, perPage)}
onChangeRowsPerPage={(pp, page) => { setPerPage(pp); load(page, pp); }}
highlightOnHover
/>
);
} Callback contract
When using server-side mode, the three callbacks are mutually exclusive. Only one fires per user interaction, never two at once:
| User action | Callback that fires | What you should do |
|---|---|---|
| Click a sortable column header | onSort(column, direction, rows) | Fetch page 1 with the new sort. Update your local sort state and reset your local page to 1. |
| Click a page number / prev / next | onChangePage(page, totalRows) | Fetch the requested page with current sort params. |
| Change the rows-per-page selector | onChangeRowsPerPage(newPerPage, currentPage) | Fetch the recalculated page at the new page size with current sort params. |
Sorting always resets to page 1. When the user sorts while on page 3, the
pagination bar jumps back to page 1 automatically and onChangePage is
not called. onSort is the sole callback, and it is your responsibility
to fetch from page 1 and keep your own page state in sync:
function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
const field = col.sortField ?? String(col.id);
setSortField(field);
setSortDir(dir);
setPage(1); // keep local state in sync with the UI reset
fetchData({ page: 1, perPage, sortField: field, sortDir: dir });
} Combined: server-side sorting and pagination
Wire all three handlers together. Because sorting resets to page 1 and suppresses
onChangePage, you only need one fetch per user action.
Server-side sort + pagination
150-row simulated dataset. Sort any column, then page through the results — each action triggers exactly one fetch (300 ms latency).
import { useState, useEffect, useCallback } from 'react';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
export default function App() {
const [data, setData] = useState<Employee[]>([]);
const [loading, setLoading] = useState(true);
const [totalRows, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [sortField, setSortField] = useState('name');
const [sortDir, setSortDir] = useState<SortOrder>(SortOrder.ASC);
const [resetPage, setResetPage] = useState(false);
const load = useCallback(async (params) => {
setLoading(true);
const result = await fetchFromServer(params);
setData(result.rows); setTotal(result.total);
setLoading(false);
}, []);
useEffect(() => { load({ page, perPage, sortField, sortDir }); }, []);
function handlePageChange(p: number) {
setPage(p);
load({ page: p, perPage, sortField, sortDir });
}
function handlePerPageChange(pp: number, p: number) {
setPerPage(pp);
load({ page: p, perPage: pp, sortField, sortDir });
}
function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
const field = col.sortField ?? String(col.id);
setSortField(field); setSortDir(dir);
setPage(1); setResetPage(prev => !prev);
load({ page: 1, perPage, sortField: field, sortDir: dir });
}
return (
<DataTable
columns={columns}
data={data}
progressPending={loading}
pagination
paginationServer
paginationTotalRows={totalRows}
paginationResetDefaultPage={resetPage}
onChangePage={handlePageChange}
onChangeRowsPerPage={handlePerPageChange}
sortServer
onSort={handleSort}
highlightOnHover
/>
);
} Full example: search, sort, and pagination
A realistic pattern with an external search input. Searching is an external state change.
It doesn't go through any of DataTable's callbacks, so toggle
paginationResetDefaultPage yourself to reset the bar to page 1.
Search + sort + pagination
200-row simulated dataset with 350 ms fake network latency. Search, sort, and page. Each action triggers exactly one fetch.
import { useState, useEffect, useCallback } from 'react';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
export default function App() {
const [data, setData] = useState<Employee[]>([]);
const [loading, setLoading] = useState(false);
const [totalRows, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [sortField, setSortField] = useState('name');
const [sortDir, setSortDir] = useState<SortOrder>(SortOrder.ASC);
const [search, setSearch] = useState('');
const [resetPage, setResetPage] = useState(false);
const load = useCallback(async (params) => {
setLoading(true);
const qs = new URLSearchParams(params);
const res = await fetch(`/api/employees?${qs}`);
const json = await res.json();
setData(json.rows); setTotal(json.total);
setLoading(false);
}, []);
useEffect(() => {
load({ page, perPage, sort: sortField, dir: sortDir, q: search });
}, []); // eslint-disable-line react-hooks/exhaustive-deps
function handlePageChange(p: number) {
setPage(p);
load({ page: p, perPage, sort: sortField, dir: sortDir, q: search });
}
function handlePerPageChange(pp: number, p: number) {
setPerPage(pp);
load({ page: p, perPage: pp, sort: sortField, dir: sortDir, q: search });
}
function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
const field = col.sortField ?? 'name';
setSortField(field); setSortDir(dir);
setPage(1);
load({ page: 1, perPage, sort: field, dir, q: search });
}
function handleSearch(q: string) {
setSearch(q);
setPage(1);
setResetPage(prev => !prev);
load({ page: 1, perPage, sort: sortField, dir: sortDir, q });
}
return (
<div>
<input
type="search"
placeholder="Search…"
value={search}
onChange={e => handleSearch(e.target.value)}
/>
<DataTable
columns={columns}
data={data}
progressPending={loading}
pagination
paginationServer
paginationTotalRows={totalRows}
paginationResetDefaultPage={resetPage}
onChangePage={handlePageChange}
onChangeRowsPerPage={handlePerPageChange}
sortServer
onSort={handleSort}
highlightOnHover
/>
</div>
);
} With React Query
Let TanStack Query
own the fetch. Update state in the callbacks and the query key change triggers a refetch
automatically. placeholderData: keepPreviousData prevents an empty flash
between page transitions.
import { useState } from 'react';
import { useQuery, keepPreviousData } from '@tanstack/react-query';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
export default function App() {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [sortField, setSortField] = useState('name');
const [sortDir, setSortDir] = useState<SortOrder>(SortOrder.ASC);
const { data, isLoading } = useQuery({
queryKey: ['employees', page, perPage, sortField, sortDir],
queryFn: () =>
fetch(`/api/employees?page=${page}&limit=${perPage}&sort=${sortField}&dir=${sortDir}`)
.then(r => r.json()),
placeholderData: keepPreviousData,
});
return (
<DataTable
columns={columns}
data={data?.rows ?? []}
progressPending={isLoading}
pagination
paginationServer
paginationTotalRows={data?.total ?? 0}
onChangePage={setPage}
onChangeRowsPerPage={(pp) => { setPerPage(pp); setPage(1); }}
sortServer
onSort={(col, dir) => {
setSortField(col.sortField ?? String(col.id));
setSortDir(dir);
setPage(1);
}}
/>
);
} With SWR
import { useState } from 'react';
import useSWR from 'swr';
import DataTable, { type TableColumn, SortOrder } from 'react-data-table-component';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export default function App() {
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [sort, setSort] = useState({ field: 'name', dir: SortOrder.ASC });
const url = `/api/employees?page=${page}&limit=${perPage}&sort=${sort.field}&dir=${sort.dir}`;
const { data, isLoading } = useSWR(url, fetcher);
return (
<DataTable
columns={columns}
data={data?.rows ?? []}
progressPending={isLoading}
pagination
paginationServer
paginationTotalRows={data?.total ?? 0}
onChangePage={setPage}
onChangeRowsPerPage={(pp) => { setPerPage(pp); setPage(1); }}
sortServer
onSort={(col, dir) => {
setSort({ field: col.sortField ?? String(col.id), dir });
setPage(1);
}}
/>
);
} Resetting to page 1
For external state changes (search, filter) that aren't triggered by a table callback,
toggle paginationResetDefaultPage to reset the bar to page 1. Any change to
this boolean (regardless of direction) triggers the reset.
const [resetPage, setResetPage] = useState(false);
function handleSearch(q: string) {
setSearch(q);
setPage(1);
setResetPage(prev => !prev);
fetchData({ page: 1, perPage, sortField, sortDir, q });
} Persisting selection across pages
By default, navigating between pages clears selected rows.
Use paginationServerOptions to retain selections across page changes and sorts:
<DataTable
pagination
paginationServer
paginationServerOptions={{
persistSelectedOnPageChange: true,
persistSelectedOnSort: true,
}}
/> Custom pagination component
Replace the built-in pagination bar entirely by passing a component to
paginationComponent. It receives rowsPerPage,
rowCount, currentPage, onChangePage, and
onChangeRowsPerPage as props.
import { type PaginationComponentProps } from 'react-data-table-component';
function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: PaginationComponentProps) {
const pages = Math.ceil(rowCount / rowsPerPage);
return (
<div>
<button disabled={currentPage === 1} onClick={() => onChangePage(currentPage - 1, rowCount)}>
‹ Prev
</button>
<span>Page {currentPage} of {pages}</span>
<button disabled={currentPage === pages} onClick={() => onChangePage(currentPage + 1, rowCount)}>
Next ›
</button>
</div>
);
}
<DataTable
columns={columns}
data={data}
pagination
paginationComponent={MyPagination}
/> Prop reference
| Prop | Type | Default | Description |
|---|---|---|---|
pagination | boolean | false | Enable the built-in pagination bar. |
paginationPerPage | number | 10 | Initial number of rows per page. |
paginationDefaultPage | number | 1 | Initial page number. |
paginationRowsPerPageOptions | number[] | [10, 25, 50, 100] | Options shown in the rows-per-page dropdown. |
paginationServer | boolean | false | Delegate pagination to the server. Use with paginationTotalRows and the change callbacks. |
paginationTotalRows | number | 0 | Total row count across all pages (server-side only). Used to calculate total pages. |
paginationResetDefaultPage | boolean | false | Toggle this value to reset the pagination bar to page 1 (e.g. after a search). |
paginationServerOptions | { persistSelectedOnPageChange?, persistSelectedOnSort? } | - | Server-side selection persistence options. |
paginationComponent | ComponentType<PaginationComponentProps> | - | Replaces the built-in pagination bar with a custom component. |
paginationComponentOptions | PaginationOptions | - | Options forwarded to the default pagination component (label customisation, etc.). |
paginationIconFirstPage | ReactNode | - | Icon for the "first page" button. |
paginationIconLastPage | ReactNode | - | Icon for the "last page" button. |
paginationIconNext | ReactNode | - | Icon for the "next page" button. |
paginationIconPrevious | ReactNode | - | Icon for the "previous page" button. |
onChangePage | (page: number, totalRows: number) => void | - | Called when the user navigates to a different page. |
onChangeRowsPerPage | (currentRowsPerPage: number, currentPage: number) => void | - | Called when the user changes the rows-per-page selection. |