MRT logoMantine React Table

React Query (Remote) Example

This is just like the Remote Data Example, but react-query is used to simplify all the state management of the fetching and loading of data.
React Query a.k.a TanStack Query is a library made by the same team that made TanStack Table and is the best way to fetch data in client-side React applications. It has a ton of features that make working with table features a lot easier. It is highly recommended that you investigate using it in your own applications.
Also, be sure to check out the Virtualized Example, which shows off the use of another TanStack library, TanStack React Virtual, to render thousands of rows at once while still maintaining great performance.
0-0 of 0
1
import { 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
type MRT_ColumnFilterFnsState,
10
} from 'mantine-react-table';
11
import { ActionIcon, Tooltip } from '@mantine/core';
12
import { IconRefresh } from '@tabler/icons-react';
13
import {
14
QueryClient,
15
QueryClientProvider,
16
useQuery,
17
} from '@tanstack/react-query';
18
19
type User = {
20
firstName: string;
21
lastName: string;
22
address: string;
23
state: string;
24
phoneNumber: string;
25
};
26
27
type UserApiResponse = {
28
data: Array<User>;
29
meta: {
30
totalRowCount: number;
31
};
32
};
33
34
interface Params {
35
columnFilterFns: MRT_ColumnFilterFnsState;
36
columnFilters: MRT_ColumnFiltersState;
37
globalFilter: string;
38
sorting: MRT_SortingState;
39
pagination: MRT_PaginationState;
40
}
41
42
//custom react-query hook
43
const useGetUsers = ({
44
columnFilterFns,
45
columnFilters,
46
globalFilter,
47
sorting,
48
pagination,
49
}: Params) => {
50
//build the URL (https://www.mantine-react-table.com/api/data?start=0&size=10&filters=[]&globalFilter=&sorting=[])
51
const fetchURL = new URL(
52
'/api/data',
53
process.env.NODE_ENV === 'production'
54
? 'https://www.mantine-react-table.com'
55
: 'http://localhost:3001',
56
);
57
fetchURL.searchParams.set(
58
'start',
59
`${pagination.pageIndex * pagination.pageSize}`,
60
);
61
fetchURL.searchParams.set('size', `${pagination.pageSize}`);
62
fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
63
fetchURL.searchParams.set(
64
'filterModes',
65
JSON.stringify(columnFilterFns ?? {}),
66
);
67
fetchURL.searchParams.set('globalFilter', globalFilter ?? '');
68
fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));
69
70
return useQuery<UserApiResponse>({
71
queryKey: ['users', fetchURL.href], //refetch whenever the URL changes (columnFilters, globalFilter, sorting, pagination)
72
queryFn: () => fetch(fetchURL.href).then((res) => res.json()),
73
keepPreviousData: true, //useful for paginated queries by keeping data from previous pages on screen while fetching the next page
74
staleTime: 30_000, //don't refetch previously viewed pages until cache is more than 30 seconds old
75
});
76
};
77
78
const Example = () => {
79
const columns = useMemo<MRT_ColumnDef<User>[]>(
80
() => [
81
{
82
accessorKey: 'firstName',
83
header: 'First Name',
84
},
85
{
86
accessorKey: 'lastName',
87
header: 'Last Name',
88
},
89
{
90
accessorKey: 'address',
91
header: 'Address',
92
},
93
{
94
accessorKey: 'state',
95
header: 'State',
96
},
97
{
98
accessorKey: 'phoneNumber',
99
header: 'Phone Number',
100
},
101
],
102
[],
103
);
104
105
//Manage MRT state that we want to pass to our API
106
const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(
107
[],
108
);
109
const [columnFilterFns, setColumnFilterFns] = //filter modes
110
useState<MRT_ColumnFilterFnsState>(
111
Object.fromEntries(
112
columns.map(({ accessorKey }) => [accessorKey, 'contains']),
113
),
114
); //default to "contains" for all columns
115
const [globalFilter, setGlobalFilter] = useState('');
116
const [sorting, setSorting] = useState<MRT_SortingState>([]);
117
const [pagination, setPagination] = useState<MRT_PaginationState>({
118
pageIndex: 0,
119
pageSize: 10,
120
});
121
122
//call our custom react-query hook
123
const { data, isError, isFetching, isLoading, refetch } = useGetUsers({
124
columnFilterFns,
125
columnFilters,
126
globalFilter,
127
pagination,
128
sorting,
129
});
130
131
//this will depend on your API response shape
132
const fetchedUsers = data?.data ?? [];
133
const totalRowCount = data?.meta?.totalRowCount ?? 0;
134
135
const table = useMantineReactTable({
136
columns,
137
data: fetchedUsers,
138
enableColumnFilterModes: true,
139
columnFilterModeOptions: ['contains', 'startsWith', 'endsWith'],
140
initialState: { showColumnFilters: true },
141
manualFiltering: true,
142
manualPagination: true,
143
manualSorting: true,
144
mantineToolbarAlertBannerProps: isError
145
? {
146
color: 'red',
147
children: 'Error loading data',
148
}
149
: undefined,
150
onColumnFilterFnsChange: setColumnFilterFns,
151
onColumnFiltersChange: setColumnFilters,
152
onGlobalFilterChange: setGlobalFilter,
153
onPaginationChange: setPagination,
154
onSortingChange: setSorting,
155
renderTopToolbarCustomActions: () => (
156
<Tooltip label="Refresh Data">
157
<ActionIcon onClick={() => refetch()}>
158
<IconRefresh />
159
</ActionIcon>
160
</Tooltip>
161
),
162
rowCount: totalRowCount,
163
state: {
164
columnFilterFns,
165
columnFilters,
166
globalFilter,
167
isLoading,
168
pagination,
169
showAlertBanner: isError,
170
showProgressBars: isFetching,
171
sorting,
172
},
173
});
174
175
return <MantineReactTable table={table} />;
176
};
177
178
const queryClient = new QueryClient();
179
180
const ExampleWithReactQueryProvider = () => (
181
//Put this with your other react-query providers near root of your app
182
<QueryClientProvider client={queryClient}>
183
<Example />
184
</QueryClientProvider>
185
);
186
187
export default ExampleWithReactQueryProvider;
1
import { useMemo, useState } from 'react';
2
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
3
import { ActionIcon, Tooltip } from '@mantine/core';
4
import { IconRefresh } from '@tabler/icons-react';
5
import {
6
QueryClient,
7
QueryClientProvider,
8
useQuery,
9
} from '@tanstack/react-query';
10
11
//custom react-query hook
12
const useGetUsers = ({
13
columnFilterFns,
14
columnFilters,
15
globalFilter,
16
sorting,
17
pagination,
18
}) => {
19
//build the URL (https://www.mantine-react-table.com/api/data?start=0&size=10&filters=[]&globalFilter=&sorting=[])
20
const fetchURL = new URL(
21
'/api/data',
22
process.env.NODE_ENV === 'production'
23
? 'https://www.mantine-react-table.com'
24
: 'http://localhost:3001',
25
);
26
fetchURL.searchParams.set(
27
'start',
28
`${pagination.pageIndex * pagination.pageSize}`,
29
);
30
fetchURL.searchParams.set('size', `${pagination.pageSize}`);
31
fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));
32
fetchURL.searchParams.set(
33
'filterModes',
34
JSON.stringify(columnFilterFns ?? {}),
35
);
36
fetchURL.searchParams.set('globalFilter', globalFilter ?? '');
37
fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));
38
39
return useQuery({
40
queryKey: ['users', fetchURL.href], //refetch whenever the URL changes (columnFilters, globalFilter, sorting, pagination)
41
queryFn: () => fetch(fetchURL.href).then((res) => res.json()),
42
keepPreviousData: true, //useful for paginated queries by keeping data from previous pages on screen while fetching the next page
43
staleTime: 30_000, //don't refetch previously viewed pages until cache is more than 30 seconds old
44
});
45
};
46
47
const Example = () => {
48
const columns = useMemo(
49
() => [
50
{
51
accessorKey: 'firstName',
52
header: 'First Name',
53
},
54
{
55
accessorKey: 'lastName',
56
header: 'Last Name',
57
},
58
{
59
accessorKey: 'address',
60
header: 'Address',
61
},
62
{
63
accessorKey: 'state',
64
header: 'State',
65
},
66
{
67
accessorKey: 'phoneNumber',
68
header: 'Phone Number',
69
},
70
],
71
[],
72
);
73
74
//Manage MRT state that we want to pass to our API
75
const [columnFilters, setColumnFilters] = useState([]);
76
const [columnFilterFns, setColumnFilterFns] = //filter modes
77
useState(
78
Object.fromEntries(
79
columns.map(({ accessorKey }) => [accessorKey, 'contains']),
80
),
81
); //default to "contains" for all columns
82
const [globalFilter, setGlobalFilter] = useState('');
83
const [sorting, setSorting] = useState([]);
84
const [pagination, setPagination] = useState({
85
pageIndex: 0,
86
pageSize: 10,
87
});
88
89
//call our custom react-query hook
90
const { data, isError, isFetching, isLoading, refetch } = useGetUsers({
91
columnFilterFns,
92
columnFilters,
93
globalFilter,
94
pagination,
95
sorting,
96
});
97
98
//this will depend on your API response shape
99
const fetchedUsers = data?.data ?? [];
100
const totalRowCount = data?.meta?.totalRowCount ?? 0;
101
102
const table = useMantineReactTable({
103
columns,
104
data: fetchedUsers,
105
enableColumnFilterModes: true,
106
columnFilterModeOptions: ['contains', 'startsWith', 'endsWith'],
107
initialState: { showColumnFilters: true },
108
manualFiltering: true,
109
manualPagination: true,
110
manualSorting: true,
111
mantineToolbarAlertBannerProps: isError
112
? {
113
color: 'red',
114
children: 'Error loading data',
115
}
116
: undefined,
117
onColumnFilterFnsChange: setColumnFilterFns,
118
onColumnFiltersChange: setColumnFilters,
119
onGlobalFilterChange: setGlobalFilter,
120
onPaginationChange: setPagination,
121
onSortingChange: setSorting,
122
renderTopToolbarCustomActions: () => (
123
<Tooltip label="Refresh Data">
124
<ActionIcon onClick={() => refetch()}>
125
<IconRefresh />
126
</ActionIcon>
127
</Tooltip>
128
),
129
rowCount: totalRowCount,
130
state: {
131
columnFilterFns,
132
columnFilters,
133
globalFilter,
134
isLoading,
135
pagination,
136
showAlertBanner: isError,
137
showProgressBars: isFetching,
138
sorting,
139
},
140
});
141
142
return <MantineReactTable table={table} />;
143
};
144
145
const queryClient = new QueryClient();
146
147
const ExampleWithReactQueryProvider = () => (
148
//Put this with your other react-query providers near root of your app
149
<QueryClientProvider client={queryClient}>
150
<Example />
151
</QueryClientProvider>
152
);
153
154
export default ExampleWithReactQueryProvider;
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