MRT logoMantine React Table

Aggregation and Grouping Example

Grouping and Aggregation features are usually hard to implement, but MRT (thanks to TanStack Table) makes it easy. Once enabled, simply click the vertical ellipses (⋮) icon for the column you want to group by and select Group by (column name).
You can group by a single column or multiple columns at a time. Then, you can run aggregations on grouped columns to calculate totals, averages, max, min, etc.
The Grouping and Aggregation features work hand in hand with the Expanding and Sorting features. Try grouping by various columns in the example below and sorting by the aggregated columns, such as "age" or "salary".
Alabama (2)Oldest by State:
42
Average by State:
$71,105
CelineJohnston42Non-binary$96,445
HarmonBode14Female$45,764
Alaska (7)Oldest by State:
79
Average by State:
$61,315
JulietUpton13Male$43,447
MartinaMiller79Male$59,714
HerbertLebsack33Female$68,645
VivianRempel30Female$89,537
RickWill36Female$48,064
GuiseppeHeller31Female$20,924
BenDooley34Non-binary$98,876
Arizona (8)Oldest by State:
77
Average by State:
$66,775
SamanthaDibbert48Male$31,884
JevonWuckert52Male$88,064
LiaLowe77Male$56,479
AbagailLuettgen25Male$99,154
AlanisKuhic5Male$93,668
ArvidAuer8Male$59,826
RosellaBlanda54Female$76,754
NathenWaelchi55Female$28,373
Max Age:
80
Average Salary:
$57,062
1-20 of 298
1
import { useMemo } from 'react';
2
import { Box, Stack } from '@mantine/core';
3
import {
4
MantineReactTable,
5
useMantineReactTable,
6
type MRT_ColumnDef,
7
} from 'mantine-react-table';
8
import { data, type Person } from './makeData';
9
10
const Example = () => {
11
const averageSalary = useMemo(
12
() => data.reduce((acc, curr) => acc + curr.salary, 0) / data.length,
13
[],
14
);
15
16
const maxAge = useMemo(
17
() => data.reduce((acc, curr) => Math.max(acc, curr.age), 0),
18
[],
19
);
20
21
const columns = useMemo<MRT_ColumnDef<Person>[]>(
22
() => [
23
{
24
header: 'First Name',
25
accessorKey: 'firstName',
26
enableGrouping: false, //do not let this column be grouped
27
},
28
{
29
header: 'Last Name',
30
accessorKey: 'lastName',
31
},
32
{
33
header: 'Age',
34
accessorKey: 'age',
35
aggregationFn: 'max', //show the max age in the group (lots of pre-built aggregationFns to choose from)
36
//required to render an aggregated cell
37
AggregatedCell: ({ cell, table }) => (
38
<>
39
Oldest by{' '}
40
{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
41
<Box
42
sx={{ color: 'skyblue', display: 'inline', fontWeight: 'bold' }}
43
>
44
{cell.getValue<number>()}
45
</Box>
46
</>
47
),
48
Footer: () => (
49
<Stack>
50
Max Age:
51
<Box color="orange">{Math.round(maxAge)}</Box>
52
</Stack>
53
),
54
},
55
{
56
header: 'Gender',
57
accessorKey: 'gender',
58
//optionally, customize the cell render when this column is grouped. Make the text blue and pluralize the word
59
GroupedCell: ({ cell, row }) => (
60
<Box sx={{ color: 'skyblue' }}>
61
<strong>{cell.getValue<string>()}s </strong> ({row.subRows?.length})
62
</Box>
63
),
64
},
65
{
66
header: 'State',
67
accessorKey: 'state',
68
},
69
{
70
header: 'Salary',
71
accessorKey: 'salary',
72
aggregationFn: 'mean',
73
//required to render an aggregated cell, show the average salary in the group
74
AggregatedCell: ({ cell, table }) => (
75
<>
76
Average by{' '}
77
{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
78
<Box sx={{ color: 'green', fontWeight: 'bold' }}>
79
{cell.getValue<number>()?.toLocaleString?.('en-US', {
80
style: 'currency',
81
currency: 'USD',
82
minimumFractionDigits: 0,
83
maximumFractionDigits: 0,
84
})}
85
</Box>
86
</>
87
),
88
//customize normal cell render on normal non-aggregated rows
89
Cell: ({ cell }) => (
90
<>
91
{cell.getValue<number>()?.toLocaleString?.('en-US', {
92
style: 'currency',
93
currency: 'USD',
94
minimumFractionDigits: 0,
95
maximumFractionDigits: 0,
96
})}
97
</>
98
),
99
Footer: () => (
100
<Stack>
101
Average Salary:
102
<Box color="orange">
103
{averageSalary?.toLocaleString?.('en-US', {
104
style: 'currency',
105
currency: 'USD',
106
minimumFractionDigits: 0,
107
maximumFractionDigits: 0,
108
})}
109
</Box>
110
</Stack>
111
),
112
},
113
],
114
[averageSalary, maxAge],
115
);
116
117
const table = useMantineReactTable({
118
columns,
119
data,
120
enableColumnResizing: true,
121
enableGrouping: true,
122
enableStickyHeader: true,
123
enableStickyFooter: true,
124
initialState: {
125
density: 'xs',
126
expanded: true,
127
grouping: ['state'],
128
pagination: { pageIndex: 0, pageSize: 20 },
129
sorting: [{ id: 'state', desc: false }],
130
},
131
mantineToolbarAlertBannerBadgeProps: { color: 'blue', variant: 'outline' },
132
mantineTableContainerProps: { sx: { maxHeight: 700 } },
133
});
134
135
return <MantineReactTable table={table} />;
136
};
137
138
export default Example;
1
import { useMemo } from 'react';
2
import { Box, Stack } from '@mantine/core';
3
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
4
import { data } from './makeData';
5
6
const Example = () => {
7
const averageSalary = useMemo(
8
() => data.reduce((acc, curr) => acc + curr.salary, 0) / data.length,
9
[],
10
);
11
12
const maxAge = useMemo(
13
() => data.reduce((acc, curr) => Math.max(acc, curr.age), 0),
14
[],
15
);
16
17
const columns = useMemo(
18
() => [
19
{
20
header: 'First Name',
21
accessorKey: 'firstName',
22
enableGrouping: false, //do not let this column be grouped
23
},
24
{
25
header: 'Last Name',
26
accessorKey: 'lastName',
27
},
28
{
29
header: 'Age',
30
accessorKey: 'age',
31
aggregationFn: 'max', //show the max age in the group (lots of pre-built aggregationFns to choose from)
32
//required to render an aggregated cell
33
AggregatedCell: ({ cell, table }) => (
34
<>
35
Oldest by{' '}
36
{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
37
<Box
38
sx={{ color: 'skyblue', display: 'inline', fontWeight: 'bold' }}
39
>
40
{cell.getValue()}
41
</Box>
42
</>
43
),
44
Footer: () => (
45
<Stack>
46
Max Age:
47
<Box color="orange">{Math.round(maxAge)}</Box>
48
</Stack>
49
),
50
},
51
{
52
header: 'Gender',
53
accessorKey: 'gender',
54
//optionally, customize the cell render when this column is grouped. Make the text blue and pluralize the word
55
GroupedCell: ({ cell, row }) => (
56
<Box sx={{ color: 'skyblue' }}>
57
<strong>{cell.getValue()}s </strong> ({row.subRows?.length})
58
</Box>
59
),
60
},
61
{
62
header: 'State',
63
accessorKey: 'state',
64
},
65
{
66
header: 'Salary',
67
accessorKey: 'salary',
68
aggregationFn: 'mean',
69
//required to render an aggregated cell, show the average salary in the group
70
AggregatedCell: ({ cell, table }) => (
71
<>
72
Average by{' '}
73
{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
74
<Box sx={{ color: 'green', fontWeight: 'bold' }}>
75
{cell.getValue()?.toLocaleString?.('en-US', {
76
style: 'currency',
77
currency: 'USD',
78
minimumFractionDigits: 0,
79
maximumFractionDigits: 0,
80
})}
81
</Box>
82
</>
83
),
84
//customize normal cell render on normal non-aggregated rows
85
Cell: ({ cell }) => (
86
<>
87
{cell.getValue()?.toLocaleString?.('en-US', {
88
style: 'currency',
89
currency: 'USD',
90
minimumFractionDigits: 0,
91
maximumFractionDigits: 0,
92
})}
93
</>
94
),
95
Footer: () => (
96
<Stack>
97
Average Salary:
98
<Box color="orange">
99
{averageSalary?.toLocaleString?.('en-US', {
100
style: 'currency',
101
currency: 'USD',
102
minimumFractionDigits: 0,
103
maximumFractionDigits: 0,
104
})}
105
</Box>
106
</Stack>
107
),
108
},
109
],
110
[averageSalary, maxAge],
111
);
112
113
const table = useMantineReactTable({
114
columns,
115
data,
116
enableColumnResizing: true,
117
enableGrouping: true,
118
enableStickyHeader: true,
119
enableStickyFooter: true,
120
initialState: {
121
density: 'xs',
122
expanded: true,
123
grouping: ['state'],
124
pagination: { pageIndex: 0, pageSize: 20 },
125
sorting: [{ id: 'state', desc: false }],
126
},
127
mantineToolbarAlertBannerBadgeProps: { color: 'blue', variant: 'outline' },
128
mantineTableContainerProps: { sx: { maxHeight: 700 } },
129
});
130
131
return <MantineReactTable table={table} />;
132
};
133
134
export default Example;
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