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".
Grouped by
State
State | First Name | Last Name | Age | Gender | Salary | |
---|---|---|---|---|---|---|
Alabama (2) | Oldest by State: 42 | Average by State: $71,105 | ||||
Celine | Johnston | 42 | Non-binary | $96,445 | ||
Harmon | Bode | 14 | Female | $45,764 | ||
Alaska (7) | Oldest by State: 79 | Average by State: $61,315 | ||||
Juliet | Upton | 13 | Male | $43,447 | ||
Martina | Miller | 79 | Male | $59,714 | ||
Herbert | Lebsack | 33 | Female | $68,645 | ||
Vivian | Rempel | 30 | Female | $89,537 | ||
Rick | Will | 36 | Female | $48,064 | ||
Guiseppe | Heller | 31 | Female | $20,924 | ||
Ben | Dooley | 34 | Non-binary | $98,876 | ||
Arizona (8) | Oldest by State: 77 | Average by State: $66,775 | ||||
Samantha | Dibbert | 48 | Male | $31,884 | ||
Jevon | Wuckert | 52 | Male | $88,064 | ||
Lia | Lowe | 77 | Male | $56,479 | ||
Abagail | Luettgen | 25 | Male | $99,154 | ||
Alanis | Kuhic | 5 | Male | $93,668 | ||
Arvid | Auer | 8 | Male | $59,826 | ||
Rosella | Blanda | 54 | Female | $76,754 | ||
Nathen | Waelchi | 55 | Female | $28,373 | ||
Max Age: 80 | Average Salary: $57,062 |
1-20 of 298
1import { useMemo } from 'react';2import { Box, Stack } from '@mantine/core';3import {4MantineReactTable,5useMantineReactTable,6type MRT_ColumnDef,7} from 'mantine-react-table';8import { data, type Person } from './makeData';910const Example = () => {11const averageSalary = useMemo(12() => data.reduce((acc, curr) => acc + curr.salary, 0) / data.length,13[],14);1516const maxAge = useMemo(17() => data.reduce((acc, curr) => Math.max(acc, curr.age), 0),18[],19);2021const columns = useMemo<MRT_ColumnDef<Person>[]>(22() => [23{24header: 'First Name',25accessorKey: 'firstName',26enableGrouping: false, //do not let this column be grouped27},28{29header: 'Last Name',30accessorKey: 'lastName',31},32{33header: 'Age',34accessorKey: 'age',35aggregationFn: 'max', //show the max age in the group (lots of pre-built aggregationFns to choose from)36//required to render an aggregated cell37AggregatedCell: ({ cell, table }) => (38<>39Oldest by{' '}40{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}41<Box42sx={{ color: 'skyblue', display: 'inline', fontWeight: 'bold' }}43>44{cell.getValue<number>()}45</Box>46</>47),48Footer: () => (49<Stack>50Max Age:51<Box color="orange">{Math.round(maxAge)}</Box>52</Stack>53),54},55{56header: 'Gender',57accessorKey: 'gender',58//optionally, customize the cell render when this column is grouped. Make the text blue and pluralize the word59GroupedCell: ({ cell, row }) => (60<Box sx={{ color: 'skyblue' }}>61<strong>{cell.getValue<string>()}s </strong> ({row.subRows?.length})62</Box>63),64},65{66header: 'State',67accessorKey: 'state',68},69{70header: 'Salary',71accessorKey: 'salary',72aggregationFn: 'mean',73//required to render an aggregated cell, show the average salary in the group74AggregatedCell: ({ cell, table }) => (75<>76Average by{' '}77{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}78<Box sx={{ color: 'green', fontWeight: 'bold' }}>79{cell.getValue<number>()?.toLocaleString?.('en-US', {80style: 'currency',81currency: 'USD',82minimumFractionDigits: 0,83maximumFractionDigits: 0,84})}85</Box>86</>87),88//customize normal cell render on normal non-aggregated rows89Cell: ({ cell }) => (90<>91{cell.getValue<number>()?.toLocaleString?.('en-US', {92style: 'currency',93currency: 'USD',94minimumFractionDigits: 0,95maximumFractionDigits: 0,96})}97</>98),99Footer: () => (100<Stack>101Average Salary:102<Box color="orange">103{averageSalary?.toLocaleString?.('en-US', {104style: 'currency',105currency: 'USD',106minimumFractionDigits: 0,107maximumFractionDigits: 0,108})}109</Box>110</Stack>111),112},113],114[averageSalary, maxAge],115);116117const table = useMantineReactTable({118columns,119data,120enableColumnResizing: true,121enableGrouping: true,122enableStickyHeader: true,123enableStickyFooter: true,124initialState: {125density: 'xs',126expanded: true,127grouping: ['state'],128pagination: { pageIndex: 0, pageSize: 20 },129sorting: [{ id: 'state', desc: false }],130},131mantineToolbarAlertBannerBadgeProps: { color: 'blue', variant: 'outline' },132mantineTableContainerProps: { sx: { maxHeight: 700 } },133});134135return <MantineReactTable table={table} />;136};137138export default Example;
1import { useMemo } from 'react';2import { Box, Stack } from '@mantine/core';3import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';4import { data } from './makeData';56const Example = () => {7const averageSalary = useMemo(8() => data.reduce((acc, curr) => acc + curr.salary, 0) / data.length,9[],10);1112const maxAge = useMemo(13() => data.reduce((acc, curr) => Math.max(acc, curr.age), 0),14[],15);1617const columns = useMemo(18() => [19{20header: 'First Name',21accessorKey: 'firstName',22enableGrouping: false, //do not let this column be grouped23},24{25header: 'Last Name',26accessorKey: 'lastName',27},28{29header: 'Age',30accessorKey: 'age',31aggregationFn: 'max', //show the max age in the group (lots of pre-built aggregationFns to choose from)32//required to render an aggregated cell33AggregatedCell: ({ cell, table }) => (34<>35Oldest by{' '}36{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}37<Box38sx={{ color: 'skyblue', display: 'inline', fontWeight: 'bold' }}39>40{cell.getValue()}41</Box>42</>43),44Footer: () => (45<Stack>46Max Age:47<Box color="orange">{Math.round(maxAge)}</Box>48</Stack>49),50},51{52header: 'Gender',53accessorKey: 'gender',54//optionally, customize the cell render when this column is grouped. Make the text blue and pluralize the word55GroupedCell: ({ cell, row }) => (56<Box sx={{ color: 'skyblue' }}>57<strong>{cell.getValue()}s </strong> ({row.subRows?.length})58</Box>59),60},61{62header: 'State',63accessorKey: 'state',64},65{66header: 'Salary',67accessorKey: 'salary',68aggregationFn: 'mean',69//required to render an aggregated cell, show the average salary in the group70AggregatedCell: ({ cell, table }) => (71<>72Average by{' '}73{table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}74<Box sx={{ color: 'green', fontWeight: 'bold' }}>75{cell.getValue()?.toLocaleString?.('en-US', {76style: 'currency',77currency: 'USD',78minimumFractionDigits: 0,79maximumFractionDigits: 0,80})}81</Box>82</>83),84//customize normal cell render on normal non-aggregated rows85Cell: ({ cell }) => (86<>87{cell.getValue()?.toLocaleString?.('en-US', {88style: 'currency',89currency: 'USD',90minimumFractionDigits: 0,91maximumFractionDigits: 0,92})}93</>94),95Footer: () => (96<Stack>97Average Salary:98<Box color="orange">99{averageSalary?.toLocaleString?.('en-US', {100style: 'currency',101currency: 'USD',102minimumFractionDigits: 0,103maximumFractionDigits: 0,104})}105</Box>106</Stack>107),108},109],110[averageSalary, maxAge],111);112113const table = useMantineReactTable({114columns,115data,116enableColumnResizing: true,117enableGrouping: true,118enableStickyHeader: true,119enableStickyFooter: true,120initialState: {121density: 'xs',122expanded: true,123grouping: ['state'],124pagination: { pageIndex: 0, pageSize: 20 },125sorting: [{ id: 'state', desc: false }],126},127mantineToolbarAlertBannerBadgeProps: { color: 'blue', variant: 'outline' },128mantineTableContainerProps: { sx: { maxHeight: 700 } },129});130131return <MantineReactTable table={table} />;132};133134export default Example;
View Extra Storybook Examples