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".

State
First Name
Last Name
Age
Gender
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

Rows per page

1-20 of 298

import '@mantine/core/styles.css';
import '@mantine/dates/styles.css'; //if using mantine date picker features
import 'mantine-react-table/styles.css'; //make sure MRT styles were imported in your app root (once)
import { useMemo } from 'react';
import { Box, Stack } from '@mantine/core';
import {
  MantineReactTable,
  useMantineReactTable,
  type MRT_ColumnDef,
} from 'mantine-react-table';
import { data, type Person } from './makeData';

const Example = () => {
  const averageSalary = useMemo(
    () => data.reduce((acc, curr) => acc + curr.salary, 0) / data.length,
    [],
  );

  const maxAge = useMemo(
    () => data.reduce((acc, curr) => Math.max(acc, curr.age), 0),
    [],
  );

  const columns = useMemo<MRT_ColumnDef<Person>[]>(
    () => [
      {
        header: 'First Name',
        accessorKey: 'firstName',
        enableGrouping: false, //do not let this column be grouped
      },
      {
        header: 'Last Name',
        accessorKey: 'lastName',
      },
      {
        header: 'Age',
        accessorKey: 'age',
        aggregationFn: 'max', //show the max age in the group (lots of pre-built aggregationFns to choose from)
        //required to render an aggregated cell
        AggregatedCell: ({ cell, table }) => (
          <>
            Oldest by{' '}
            {table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
            <Box
              style={{
                color: 'skyblue',
                display: 'inline',
                fontWeight: 'bold',
              }}
            >
              {cell.getValue<number>()}
            </Box>
          </>
        ),
        Footer: () => (
          <Stack>
            Max Age:
            <Box color="orange">{Math.round(maxAge)}</Box>
          </Stack>
        ),
      },
      {
        header: 'Gender',
        accessorKey: 'gender',
        //optionally, customize the cell render when this column is grouped. Make the text blue and pluralize the word
        GroupedCell: ({ cell, row }) => (
          <Box style={{ color: 'skyblue' }}>
            <strong>{cell.getValue<string>()}s </strong> ({row.subRows?.length})
          </Box>
        ),
      },
      {
        header: 'State',
        accessorKey: 'state',
      },
      {
        header: 'Salary',
        accessorKey: 'salary',
        aggregationFn: 'mean',
        //required to render an aggregated cell, show the average salary in the group
        AggregatedCell: ({ cell, table }) => (
          <>
            Average by{' '}
            {table.getColumn(cell.row.groupingColumnId ?? '').columnDef.header}:{' '}
            <Box style={{ color: 'green', fontWeight: 'bold' }}>
              {cell.getValue<number>()?.toLocaleString?.('en-US', {
                style: 'currency',
                currency: 'USD',
                minimumFractionDigits: 0,
                maximumFractionDigits: 0,
              })}
            </Box>
          </>
        ),
        //customize normal cell render on normal non-aggregated rows
        Cell: ({ cell }) => (
          <>
            {cell.getValue<number>()?.toLocaleString?.('en-US', {
              style: 'currency',
              currency: 'USD',
              minimumFractionDigits: 0,
              maximumFractionDigits: 0,
            })}
          </>
        ),
        Footer: () => (
          <Stack>
            Average Salary:
            <Box color="orange">
              {averageSalary?.toLocaleString?.('en-US', {
                style: 'currency',
                currency: 'USD',
                minimumFractionDigits: 0,
                maximumFractionDigits: 0,
              })}
            </Box>
          </Stack>
        ),
      },
    ],
    [averageSalary, maxAge],
  );

  const table = useMantineReactTable({
    columns,
    data,
    enableColumnResizing: true,
    enableGrouping: true,
    enableStickyHeader: true,
    enableStickyFooter: true,
    initialState: {
      density: 'xs',
      expanded: true,
      grouping: ['state'],
      pagination: { pageIndex: 0, pageSize: 20 },
      sorting: [{ id: 'state', desc: false }],
    },
    mantineToolbarAlertBannerBadgeProps: { color: 'blue', variant: 'outline' },
    mantineTableContainerProps: { style: { maxHeight: 700 } },
  });

  return <MantineReactTable table={table} />;
};

export default Example;

View Extra Storybook Examples