MRT logoMantine React Table

On This Page

    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.
    No records to display
    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