MRT logoMaterial React Table

    Aggregation and Grouping Feature Guide

    Material React Table has built-in grouping and aggregation features. There are options for both automatic client-side grouping and aggregation, as well as manual server-side grouping and aggregation. This guide will walk you through the different options and how to use and customize them.

    Relevant Props

    #
    Prop Name
    Type
    Default Value
    More Info Links
    1
    Record<string, AggregationFn>
    TanStack Table Grouping Docs

    This option allows you to define custom aggregation functions that can be referenced in a column's aggegationFn option by their key

    2
    boolean
    true

    No Description Provided... Yet...

    3
    boolean

    No Description Provided... Yet...

    4
    boolean

    No Description Provided... Yet...

    5

    No Description Provided... Yet...

    6

    No Description Provided... Yet...

    7

    No Description Provided... Yet...

    8

    No Description Provided... Yet...

    9

    No Description Provided... Yet...

    Relevant Column Options

    #
    Column Option
    Type
    Default Value
    More Info Links
    1
    (({ cell, column, row, table }) => ReactNode)

    Define a custom cell render for an aggregated cell.

    2
    ReactNode | (({ column, footer, table }) => ReactNode)
    MRT Data Columns Docs

    Render custom markup for a column footer.

    3
    (({ cell, column, row, table }) => ReactNode)

    Define a custom cell render for a grouped cell.

    4
    boolean

    No Description Provided... Yet...

    Relevant State

    #
    State Option
    Type
    Default Value
    More Info Links
    1
    Record<string, boolean> | boolean
    {}
    TanStack Table Expanding Docs

    No Description Provided... Yet...

    2
    Array<string>
    []
    TanStack Table Grouping Docs

    No Description Provided... Yet...

    Enable Grouping

    To enable grouping, set the enableGrouping prop to true. This will both add a drag handle button so columns can be dragged to the dropzone to be grouped, and will add an entry column actions menu to group or ungroup a column.

    <MaterialReactTable columns={columns} data={data} enableGrouping />

    Disable Grouping Per Column

    const columns = [
    {
    accessorKey: 'name',
    header: 'Name',
    enableGrouping: false, // disable grouping for this column
    },
    {
    accessorKey: 'age',
    header: 'Age',
    },
    ];
    return <MaterialReactTable columns={columns} data={data} enableGrouping />;

    Hide Drag Buttons for Grouping

    If you don't want the drag buttons that come with the grouping feature, you can independently disable them without disabling the grouping feature entirely by setting the enableColumnDragging prop to false.

    <MaterialReactTable
    columns={columns}
    data={data}
    enableGrouping
    enableColumnDragging={false} //don't show drag handle buttons, but still show grouping options in column actions menu
    />

    Group Columns by Default

    If you want columns to be grouped by default, you can set the grouping state in either the initialState or state prop.

    <MaterialReactTable
    columns={columns}
    data={data}
    enableGrouping
    initialState={{ grouping: ['location', 'department'] }} //group by location and department by default
    />

    Expand Grouped Rows by Default

    In addition to grouping columns by default, you may also want those grouped rows to be expanded and visible by default too. You can do this by setting the expanded state to true in either the initialState or state prop.

    <MaterialReactTable
    columns={columns}
    data={data}
    enableGrouping
    initialState={{
    grouping: ['location', 'department'], //group by location and department by default and expand grouped rows
    expanded: true, //show grouped rows by default
    }}
    />

    Aggregation on Grouped Rows

    One of the cool features of Material React Table is that it can automatically aggregate the data in grouped rows. To enable this, you must specify both an aggregationFn and an AggregatedCell render option on a column definition.

    Built-in Aggregation Functions

    There are several built-in aggregation functions available that you can use. They are:

    • count - Finds the number of rows in a group

    • extent - Finds the minimum and maximum values of a group of rows

    • max - Finds the maximum value of a group of rows

    • mean - Finds the average value of a group of rows

    • median - Finds the median value of a group of rows

    • min - Finds the minimum value of a group of rows

    • sum - sums the values of a group of rows

    • uniqueCount - Finds the number of unique values of a group of rows

    • unique - Finds the unique values of a group of rows

    All of these built-in aggregation functions are from TanStack Table

    const columns = [
    {
    accessorKey: 'team', //grouped by team in initial state below
    header: 'Team',
    },
    {
    accessorKey: 'player',
    header: 'Player',
    },
    {
    accessorKey: 'points',
    header: 'Points',
    aggregationFn: 'sum', //calc total points for each team by adding up all the points for each player on the team
    AggregatedCell: ({ cell }) => <div>Team Score: {cell.getValue()}</div>,
    },
    ];
    return (
    <MaterialReactTable
    columns={columns}
    data={data}
    enableGrouping
    initialState={{ grouping: ['team'], expanded: true }}
    />
    );

    Custom Aggregation Functions

    If none of those pre-built aggregation functions work for you, you can also pass in a custom aggregation function. The aggregation function will be passed an array of values from the column that you are aggregating. It should return a single value that will be displayed in the aggregated cell.

    If you are specifying a custom aggregation function, it must implement the following type:

    export type AggregationFn<TData extends AnyData> = (
    getLeafRows: () => Row<TData>[],
    getChildRows: () => Row<TData>[]
    ) => any

    Material React Table does not automatically aggregate all rows for you to calculate totals for the entire table. However, it is still easy enough to do this manually and add in your custom calculations into the footer or Footer of a column definition. It is recommended that you do any necessary aggregation calculations on your data in a useMemo hook before passing it to the columns footer in your columns definition.

    //calculate the total points for all players in the table in a useMemo hook
    const averageScore = useMemo(() => {
    const totalPoints = data.reduce((acc, row) => acc + row.points, 0);
    const totalPlayers = data.length;
    return totalPoints / totalPlayers;
    }, [data]);
    const columns = [
    {
    accessorKey: 'name',
    header: 'Name',
    },
    {
    accessorKey: 'score',
    header: 'Score',
    Footer: () => <div>Average Score: {averageScore}</div>, //don't do calculations in render, do them in useMemo hook and pass them in here
    },
    ];

    Please remember to perform heavy aggregation calculations in a useMemo hook to avoid unnecessary re-renders!

    Custom Cell Renders for Aggregation and Grouping

    There are a few custom cell render overrides that you should be aware of when using grouping and aggregation features.

    AggregatedCell Column Option

    "Aggregation Cells" are cells in an aggregated row (not a normal data row) that can display aggregates (avg, sum, etc) of the data in a group. The cell that the table is grouped on, however, is not an Aggregate Cell, but rather a GroupedCell.

    You can specify the custom render for these cells with the AggregatedCell render option on a column definition.

    const columns = [
    {
    accessorKey: 'points',
    header: 'Points',
    aggregationFn: 'sum',
    AggregatedCell: ({ cell }) => <div>Total Score: {cell.getValue()}</div>,
    },
    ];

    GroupedCell Column Option

    "Grouped Cells" are cells in a grouped row (not a normal data row) that by default display the value that the rows are grouped on, and the number of rows in the group. You can override the default render for these cells with the GroupedCell render option on a column definition.

    const columns = [
    {
    accessorKey: 'team',
    header: 'Team',
    GroupedCell: ({ cell }) => <div>Team: {cell.getValue()}</div>,
    },
    ];

    Aggregation/Grouping Example


    Demo

    Open Code SandboxOpen on GitHub
    State
    First Name
    Last Name
    Age
    Gender
    Salary
    Alabama (7)Oldest by State:
    64
    Average by State:
    $43,375
    ThadWiegand64Female$56,146
    AliviaLedner56Male$12,591
    DanykaGleason36Male$71,238
    LionelHartmann30Nonbinary$58,743
    ReinholdReichel30Female$30,531
    LurlineKoepp59Female$10,645
    KodyBraun38Female$63,733
    Alaska (8)Oldest by State:
    59
    Average by State:
    $68,901
    EloisaKohler31Male$45,801
    KianHand56Male$81,062
    LoyceSchmidt29Female$76,295
    MichaleCollier59Male$75,197
    EldridgeStroman42Male$59,594
    AlveraBalistreri25Female$79,844
    KaydenEmard35Female$98,252
    DomingoBauch36Female$35,159
    Arizona (1)Oldest by State:
    22
    Average by State:
    $54,027
    GunnerRolfson22Male$54,027
    Arkansas (4)Oldest by State:
    52
    Average by State:
    $58,194

    Rows per page

    1-20 of 249

    Source Code

    1import React, { FC, useMemo } from 'react';
    2import { Box, Stack } from '@mui/material';
    3import MaterialReactTable, { MRT_ColumnDef } from 'material-react-table';
    4import { data, Person } from './makeData';
    5
    6const Example: FC = () => {
    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<MRT_ColumnDef<Person>[]>(
    18 () => [
    19 {
    20 header: 'First Name',
    21 accessorKey: 'firstName',
    22 enableGrouping: false, //don't 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: 'info.main', display: 'inline', fontWeight: 'bold' }}
    39 >
    40 {cell.getValue<number>()}
    41 </Box>
    42 </>
    43 ),
    44 Footer: () => (
    45 <Stack>
    46 Max Age:
    47 <Box color="warning.main">{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: 'primary.main' }}>
    57 <strong>{cell.getValue<string>()}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: 'success.main', fontWeight: 'bold' }}>
    75 {cell.getValue<number>()?.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<number>()?.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="warning.main">
    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 return (
    114 <MaterialReactTable
    115 columns={columns}
    116 data={data}
    117 enableGrouping
    118 enableStickyHeader
    119 enableStickyFooter
    120 initialState={{
    121 density: 'compact',
    122 expanded: true, //expand all groups by default
    123 grouping: ['state'], //an array of columns to group by by default (can be multiple)
    124 pagination: { pageIndex: 0, pageSize: 20 },
    125 sorting: [{ id: 'state', desc: false }], //sort by state by default
    126 }}
    127 muiToolbarAlertBannerChipProps={{ color: 'primary' }}
    128 muiTableContainerProps={{ sx: { maxHeight: 700 } }}
    129 />
    130 );
    131};
    132
    133export default Example;
    134

    Manual Grouping

    TODO