MRT logoMantine React Table

Remote Data Fetching Example

You will most likely be using a remote data source for your table, which is fully supported. Here is an example of data being fetched from a remote server but also filtered, paginated, and sorted on the server.
Also, be sure to check out the TanStack React Query Example, which is very similar to this one, except it uses react-query to simplify much of the state management needed for fetching data.
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