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>
);
}Pagination position
By default the pagination bar renders below the table. Use paginationPosition to move it above the table ("top") or show it in both places ("both").
Pagination position
Toggle between top, bottom, and both to see the pagination bar reposition.
<DataTable
columns={columns}
data={data}
pagination
paginationPosition="top" // "top" | "bottom" | "both"
/>;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 andonChangeRowsPerPage 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 isnot 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 suppressesonChangePage, 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 togglepaginationResetDefaultPage 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 Queryown 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 topaginationComponent. It receives rowsPerPage,rowCount, currentPage, onChangePage, andonChangeRowsPerPage 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} />;Localization
All navigation aria-labels on the built-in pagination bar are controlled via thelocalization prop. Use a pre-built locale or supply your own — every key is optional and falls back to the English default. Visible text such asrowsPerPageText and rangeSeparatorText is still set viapaginationComponentOptions.
// Drop-in locale
import DataTable from 'react-data-table-component';
import { fr } from 'react-data-table-component/locales';
<DataTable columns={columns} data={data} pagination localization={fr} />;// Partial override — spread a locale and replace only what you need
import { fr } from 'react-data-table-component/locales';
import type { Localization } from 'react-data-table-component';
const myLocale: Localization = {
...fr,
pagination: {
...fr.pagination,
nextPageAriaLabel: 'Suivant',
},
};
<DataTable columns={columns} data={data} pagination localization={myLocale} />;// Built from scratch — every key is optional
import type { Localization } from 'react-data-table-component';
const custom: Localization = {
pagination: {
navigationAriaLabel: 'Table navigation',
firstPageAriaLabel: 'First',
previousPageAriaLabel: 'Prev',
nextPageAriaLabel: 'Next',
lastPageAriaLabel: 'Last',
},
};
<DataTable columns={columns} data={data} pagination localization={custom} />;Prop reference
| Prop | Type | Default | Description |
|---|---|---|---|
pagination | boolean | false | Enable built-in pagination |
paginationPerPage | number | 10 | Rows shown per page. |
paginationPosition | 'top' | 'bottom' | 'both' | 'bottom' | Where the pagination bar renders. 'both' shows it above and below the table simultaneously. |
paginationRowsPerPageOptions | number[] | [10,15,20,25,30] | Options in the rows-per-page dropdown. |
paginationDefaultPage | number | 1 | Initial active page. |
paginationPage | number | - | Controlled active page. When provided, the table navigates to this page whenever the value changes. Use together with onChangePage to keep them in sync. |
paginationResetDefaultPage | boolean | false | Toggle to reset to page 1 (e.g. after a filter change). Prefer paginationPage for new code. |
paginationTotalRows | number | - | Total row count for server-side pagination. |
paginationServer | boolean | false | Delegate page changes to onChangePage / onChangeRowsPerPage. |
paginationServerOptions | PaginationServerOptions | - | Selection-persistence options for server-side mode. |
paginationComponentOptions | PaginationOptions | - | Localisation and display options for the built-in paginator. |
paginationComponent | React.ComponentType | - | Replace the built-in pagination UI entirely. |
paginationIcons | PaginationIcons | - | Override any or all pagination icons. Pass a partial object: { next: <MyIcon /> }. |
onChangePage | (page, totalRows) => void | - | Called when the active page changes. |
onChangeRowsPerPage | (rowsPerPage, page) => void | - | Called when rows-per-page selection changes. |
localization | Localization | - | Override every user-visible string and aria-label in the table. Pass a pre-built locale or build your own. |
See it combined with other features in the Server-side sort, page & filter recipe and URL-synced table state.