MRT logoMantine React Table

Pagination Feature Guide

Client-side pagination is enabled by default in Mantine React Table. There are a number of ways to customize pagination, turn off pagination, or completely replace the built in pagination with your own manual or server-side pagination logic.

Relevant Table Options

1
boolean
TanStack Table Pagination Docs
2
boolean
true
3
() => MRT_RowModel<TData>
4
PaginationProps & { rowsPerPageOptions?: string[], showRowsPerPage?: boolean; }
Mantine Pagination Docs
5
boolean
TanStack Table Pagination Docs
6
OnChangeFn<PaginationState>
TanStack Table Pagination Docs
7
number
TanStack Table Pagination Docs
8
boolean
TanStack Table Expanding Docs
9
'default' | 'pages' | 'custom'
'default'
MRT Editing Docs
10
'bottom' | 'top' | 'both'
11
number

Relevant State Options

1
{ pageIndex: number, pageSize: number }
{ pageIndex: 0, pageSize: 10 }
TanStack Table Pagination Docs

Disable Pagination

If you simply want to disable pagination, you can set the enablePagination table option to false. This will both hide the pagination controls and disable the pagination functionality.
If you only want to disable the pagination logic, but still want to show and use the pagination controls, take a look down below at the Manual Pagination docs.
const table = useMantineReactTable({
columns,
data,
enablePagination: false,
enableBottomToolbar: false, //hide the bottom toolbar as well if you want
});

Customize Pagination

Customize Pagination Behavior

There are a few table options that you can use to customize the pagination behavior. The first one is autoResetPageIndex. This table option is true by default, and causes a table to automatically reset the table back to the first page whenever sorting, filtering, or grouping occurs. This makes sense for most use cases, but if you want to disable this behavior, you can set this table option to false.
Next there is paginateExpandedRows, which works in conjunction expanding features. This table option is true by default, and forces the table to still only render the same number of rows per page that is set as the page size, even as sub-rows become expanded. However, this does cause expanded rows to sometimes not be on the same page as their parent row, so you can turn this off to keep sub rows with their parent row on the same page.

Customize Pagination Components

You can customize the pagination component with the mantinePaginationProps table option to change things like the rowsPerPageOptions or whether or not to show the first and last page buttons, and more.
const table = useMantineReactTable({
columns,
data,
mantinePaginationProps: {
rowsPerPageOptions: ['5', '10'],
withEdges: false, //note: changed from `showFirstLastButtons` in v1.0
},
});

Alternate Pagination UI

By default, Mantine React Table provides its own Table Pagination UI that is more compact and traditional for data tables. However, if you want to use the Mantine Pagination component instead, it is as easy as setting the paginationDisplayMode table option to pages.
const table = useMantineReactTable({
columns,
data,
paginationDisplayMode: 'pages',
});
MasonAndersonmanderson57@yopmail.comSeattle
NoraBishopnbishop26@mailinator.comPortland
LiamPattersonlpatterson61@yopmail.comAustin
HarperRosshross38@mailinator.comChicago
OliverBakerobaker72@yopmail.comMiami
CharlottePhillipscphillips33@mailinator.comLos Angeles
HenryCooperhcooper18@yopmail.comDenver
EmmaJenkinsejenkins49@mailinator.comBoston
AlexanderGonzalezagonzalez67@yopmail.comDallas
AvaRamirezaramirez94@mailinator.comHouston
1
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
2
import { columns, data } from './makeData';
3
4
const Example = () => {
5
const table = useMantineReactTable({
6
columns,
7
data,
8
mantinePaginationProps: {
9
showRowsPerPage: false,
10
},
11
paginationDisplayMode: 'pages',
12
});
13
14
return <MantineReactTable table={table} />;
15
};
16
17
export default Example;
1
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
2
import { columns, data } from './makeData';
3
4
const Example = () => {
5
const table = useMantineReactTable({
6
columns,
7
data,
8
mantinePaginationProps: {
9
showRowsPerPage: false,
10
},
11
paginationDisplayMode: 'mantine',
12
});
13
14
return <MantineReactTable table={table} />;
15
};
16
17
export default Example;

Manual or Server-Side Pagination

Manual Pagination

The default pagination features are client-side. This means you have to have all of your data fetched and stored in the table all at once. This may not be ideal for large datasets, but do not worry, Mantine React Table supports server-side pagination.
When the manualPagination table option is set to true, Mantine React Table will assume that the data that is passed to the table already has had the pagination logic applied. Usually you would do this in your back-end logic.

Override Page Count and Row Count

If you are using manual pagination, the default page count and row count in the MRT Pagination component will be incorrect, as it is only derived from the number of rows provided in the client-side data table option. Luckily, you can override these values and set your own page count or row count in the pageCount and rowCount table options.
const table = useMantineReactTable({
columns,
data,
manualPagination: true,
rowCount: data.meta.totalDBRowCount, //you can tell the pagination how many rows there are in your back-end data
});

Manage Pagination State

For either client-side or server-side pagination, you may want to have access to the pagination state yourself. You can do this like so with state:
//store pagination state in your own state
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 5, //customize the default page size
});
useEffect(() => {
//do something when the pagination state changes
}, [pagination.pageIndex, pagination.pageSize]);
const table = useMantineReactTable({
columns,
data,
onPaginationChange: setPagination, //hoist pagination state to your state when it changes internally
state: { pagination }, //pass the pagination state to the table
});
return <MantineReactTable table={table} />;
Alternatively, if all you care about is customizing the initial pagination state and do not need to react to its changes, like customizing the default page size or the page index, you can do that like so with initialState:
const table = useMantineReactTable({
columns,
data,
initialState: { pagination: { pageSize: 25, pageIndex: 2 } },
});
Here is the full Remote Data example showing off server-side filtering, pagination, and sorting.
0-0 of 0
1
import { useEffect, useMemo, useState } from 'react';
2
import {
3
MantineReactTable,
4
useMantineReactTable,
5
type MRT_ColumnDef,
6
type MRT_ColumnFiltersState,
7
type MRT_PaginationState,
8
type MRT_SortingState,
9
} from 'mantine-react-table';
10
11
type UserApiResponse = {
12
data: Array<User>;
13
meta: {
14
totalRowCount: number;
15
};
16
};
17
18
type User = {
19
firstName: string;
20
lastName: string;
21
address: string;
22
state: string;
23
phoneNumber: string;
24
};
25
26
const Example = () => {
27
//data and fetching state
28
const [data, setData] = useState<User[]>([]);
29
const [isError, setIsError] = useState(false);
30
const [isLoading, setIsLoading] = useState(false);
31
const [isRefetching, setIsRefetching] = useState(false);
32
const [rowCount, setRowCount] = useState(0);
33
34
//table state
35
const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(
36
[],
37
);
38
const [globalFilter, setGlobalFilter] = useState('');
39
const [sorting, setSorting] = useState<MRT_SortingState>([]);
40
const [pagination, setPagination] = useState<MRT_PaginationState>({
41
pageIndex: 0,
42
pageSize: 10,
43
});
44
45
//if you want to avoid useEffect, look at the React Query example instead
46
useEffect(() => {
47
const fetchData = async () => {
48
if (!data.length) {
49
setIsLoading(true);
50
} else {
51
setIsRefetching(true);
52
}
53
54
const url = new URL(
55
'/api/data',
56
process.env.NODE_ENV === 'production'
57
? 'https://www.mantine-react-table.com'
58
: 'http://localhost:3001',
59
);
60
url.searchParams.set(
61
'start',
62
`${pagination.pageIndex * pagination.pageSize}`,
63
);
64
url.searchParams.set('size', `${pagination.pageSize}`);
65
url.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
66
url.searchParams.set('globalFilter', globalFilter ?? '');
67
url.searchParams.set('sorting', JSON.stringify(sorting ?? []));
68
69
try {
70
const response = await fetch(url.href);
71
const json = (await response.json()) as UserApiResponse;
72
setData(json.data);
73
setRowCount(json.meta.totalRowCount);
74
} catch (error) {
75
setIsError(true);
76
console.error(error);
77
return;
78
}
79
setIsError(false);
80
setIsLoading(false);
81
setIsRefetching(false);
82
};
83
fetchData();
84
// eslint-disable-next-line react-hooks/exhaustive-deps
85
}, [
86
columnFilters, //refetch when column filters change
87
globalFilter, //refetch when global filter changes
88
pagination.pageIndex, //refetch when page index changes
89
pagination.pageSize, //refetch when page size changes
90
sorting, //refetch when sorting changes
91
]);
92
93
const columns = useMemo<MRT_ColumnDef<User>[]>(
94
() => [
95
{
96
accessorKey: 'firstName',
97
header: 'First Name',
98
},
99
100
{
101
accessorKey: 'lastName',
102
header: 'Last Name',
103
},
104
{
105
accessorKey: 'address',
106
header: 'Address',
107
},
108
{
109
accessorKey: 'state',
110
header: 'State',
111
},
112
{
113
accessorKey: 'phoneNumber',
114
header: 'Phone Number',
115
},
116
],
117
[],
118
);
119
120
const table = useMantineReactTable({
121
columns,
122
data,
123
enableRowSelection: true,
124
getRowId: (row) => row.phoneNumber,
125
initialState: { showColumnFilters: true },
126
manualFiltering: true,
127
manualPagination: true,
128
manualSorting: true,
129
rowCount,
130
onColumnFiltersChange: setColumnFilters,
131
onGlobalFilterChange: setGlobalFilter,
132
onPaginationChange: setPagination,
133
onSortingChange: setSorting,
134
state: {
135
columnFilters,
136
globalFilter,
137
isLoading,
138
pagination,
139
showAlertBanner: isError,
140
showProgressBars: isRefetching,
141
sorting,
142
},
143
mantineToolbarAlertBannerProps: isError
144
? { color: 'red', children: 'Error loading data' }
145
: undefined,
146
});
147
148
return <MantineReactTable table={table} />;
149
};
150
151
export default Example;
1
import { useEffect, useMemo, useState } from 'react';
2
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
3
4
const Example = () => {
5
//data and fetching state
6
const [data, setData] = useState([]);
7
const [isError, setIsError] = useState(false);
8
const [isLoading, setIsLoading] = useState(false);
9
const [isRefetching, setIsRefetching] = useState(false);
10
const [rowCount, setRowCount] = useState(0);
11
12
//table state
13
const [columnFilters, setColumnFilters] = useState([]);
14
const [globalFilter, setGlobalFilter] = useState('');
15
const [sorting, setSorting] = useState([]);
16
const [pagination, setPagination] = useState({
17
pageIndex: 0,
18
pageSize: 10,
19
});
20
21
//if you want to avoid useEffect, look at the React Query example instead
22
useEffect(() => {
23
const fetchData = async () => {
24
if (!data.length) {
25
setIsLoading(true);
26
} else {
27
setIsRefetching(true);
28
}
29
30
const url = new URL(
31
'/api/data',
32
process.env.NODE_ENV === 'production'
33
? 'https://www.mantine-react-table.com'
34
: 'http://localhost:3001',
35
);
36
url.searchParams.set(
37
'start',
38
`${pagination.pageIndex * pagination.pageSize}`,
39
);
40
url.searchParams.set('size', `${pagination.pageSize}`);
41
url.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
42
url.searchParams.set('globalFilter', globalFilter ?? '');
43
url.searchParams.set('sorting', JSON.stringify(sorting ?? []));
44
45
try {
46
const response = await fetch(url.href);
47
const json = await response.json();
48
setData(json.data);
49
setRowCount(json.meta.totalRowCount);
50
} catch (error) {
51
setIsError(true);
52
console.error(error);
53
return;
54
}
55
setIsError(false);
56
setIsLoading(false);
57
setIsRefetching(false);
58
};
59
fetchData();
60
// eslint-disable-next-line react-hooks/exhaustive-deps
61
}, [
62
columnFilters, //refetch when column filters change
63
globalFilter, //refetch when global filter changes
64
pagination.pageIndex, //refetch when page index changes
65
pagination.pageSize, //refetch when page size changes
66
sorting, //refetch when sorting changes
67
]);
68
69
const columns = useMemo(
70
() => [
71
{
72
accessorKey: 'firstName',
73
header: 'First Name',
74
},
75
76
{
77
accessorKey: 'lastName',
78
header: 'Last Name',
79
},
80
{
81
accessorKey: 'address',
82
header: 'Address',
83
},
84
{
85
accessorKey: 'state',
86
header: 'State',
87
},
88
{
89
accessorKey: 'phoneNumber',
90
header: 'Phone Number',
91
},
92
],
93
[],
94
);
95
96
const table = useMantineReactTable({
97
columns,
98
data,
99
enableRowSelection: true,
100
getRowId: (row) => row.phoneNumber,
101
initialState: { showColumnFilters: true },
102
manualFiltering: true,
103
manualPagination: true,
104
manualSorting: true,
105
rowCount,
106
onColumnFiltersChange: setColumnFilters,
107
onGlobalFilterChange: setGlobalFilter,
108
onPaginationChange: setPagination,
109
onSortingChange: setSorting,
110
state: {
111
columnFilters,
112
globalFilter,
113
isLoading,
114
pagination,
115
showAlertBanner: isError,
116
showProgressBars: isRefetching,
117
sorting,
118
},
119
mantineToolbarAlertBannerProps: isError
120
? { color: 'red', children: 'Error loading data' }
121
: undefined,
122
});
123
124
return <MantineReactTable table={table} />;
125
};
126
127
export default Example;
1
import {
2
type MRT_ColumnFiltersState,
3
type MRT_SortingState,
4
} from 'mantine-react-table';
5
import { type NextApiRequest, type NextApiResponse } from 'next';
6
import { getData } from './mock';
7
8
//This is just a simple mock of a backend API where you would do server-side pagination, filtering, and sorting
9
//You would most likely want way more validation and error handling than this in a real world application
10
//Also most of this logic should actually be in the database query itself, but this is just a mock
11
export default function handler(req: NextApiRequest, res: NextApiResponse) {
12
let dbData = getData();
13
const { start, size, filters, filterModes, sorting, globalFilter } =
14
req.query as Record<string, string>;
15
16
const parsedFilterModes = JSON.parse(filterModes ?? '{}') as Record<
17
string,
18
string
19
>;
20
21
const parsedColumnFilters = JSON.parse(filters) as MRT_ColumnFiltersState;
22
if (parsedColumnFilters?.length) {
23
parsedColumnFilters.map((filter) => {
24
const { id: columnId, value: filterValue } = filter;
25
const filterMode = parsedFilterModes?.[columnId] ?? 'contains';
26
dbData = dbData.filter((row) => {
27
const rowValue = row[columnId]?.toString()?.toLowerCase();
28
if (filterMode === 'contains') {
29
return rowValue.includes?.((filterValue as string).toLowerCase());
30
} else if (filterMode === 'startsWith') {
31
return rowValue.startsWith?.((filterValue as string).toLowerCase());
32
} else if (filterMode === 'endsWith') {
33
return rowValue.endsWith?.((filterValue as string).toLowerCase());
34
}
35
});
36
});
37
}
38
39
if (globalFilter) {
40
dbData = dbData.filter((row) =>
41
Object.keys(row).some(
42
(columnId) =>
43
row[columnId]
44
?.toString()
45
?.toLowerCase()
46
?.includes?.((globalFilter as string).toLowerCase()),
47
),
48
);
49
}
50
51
const parsedSorting = JSON.parse(sorting) as MRT_SortingState;
52
if (parsedSorting?.length) {
53
const sort = parsedSorting[0];
54
const { id, desc } = sort;
55
dbData.sort((a, b) => {
56
if (desc) {
57
return a[id] < b[id] ? 1 : -1;
58
}
59
return a[id] > b[id] ? 1 : -1;
60
});
61
}
62
63
res.status(200).json({
64
data:
65
dbData?.slice(parseInt(start), parseInt(start) + parseInt(size)) ?? [],
66
meta: { totalRowCount: dbData.length },
67
});
68
}
View Extra Storybook Examples
You can help make these docs better! PRs are Welcome
Using Material-UI instead of Mantine?
Check out Material React Table