Expanding Sub-Rows (Tree Data) Feature Guide
Material React Table has support for expanding sub-rows or tree data. This feature is useful for displaying hierarchical data. The sub-rows can be expanded and collapsed by clicking on the expand/collapse icon.
NOTE: This feature is for expanding rows of the same data type. If you want to add expansion of more data for the same row, check out the Detail Panel Feature Guide.
Relevant Table Options
# | Prop Name | Type | Default Value | More Info Links | |
---|---|---|---|---|---|
1 |
| TanStack Table Expanding Docs | |||
2 |
| Usage Docs | |||
3 |
|
| MRT Expanding Sub Rows Docs | ||
4 |
| MRT Expanding Sub Rows Docs | |||
5 |
| ||||
6 |
|
| TanStack Filtering Docs | ||
7 |
| ||||
8 |
| TanStack Table Expanding Docs | |||
9 |
| TanStack Table Expanding Docs | |||
10 |
| TanStack Table Core Table Docs | |||
11 |
| TanStack Table Expanding Docs | |||
12 |
|
| TanStack Table Filtering Docs | ||
13 |
| Material UI IconButton Props | |||
14 |
| Material UI IconButton Props | |||
15 |
| TanStack Table Expanding Docs | |||
16 |
| TanStack Table Expanding Docs | |||
17 |
| ||||
Relevant State Options
# | State Option | Type | Default Value | More Info Links | |
---|---|---|---|---|---|
1 |
|
| TanStack Table Expanding Docs | ||
Enable Expanding Sub-Rows
To enable expanding sub-rows, you must first set the enableExpanding
table option to true
.
However, your data must also be formatted in a way to allow for expanding rows that are in some way related to each other. By default, Material React Table will look for a special subRows
property on each row of your data and treat any array of rows that it finds as the sub-rows for that row. You can customize or override this behavior by passing a custom getSubRows
table option.
const data = [{id: 1,name: 'John Doe',subRows: [{id: 2,name: 'Jane Doe',},//more sub rows...],},//more rows...];const table = useMaterialReactTable({columns,data,enableExpanding: true,getSubRows: (originalRow) => originalRow.subRows, //default, can customize});return <MaterialReactTable table={table} />;
Expand All Rows Button
By default, Material React Table will show the expand all button in the expand column header. You can disable this by setting the enableExpandAll
table option to false
.
const table = useMaterialReactTable({columns,data,enableExpanding: true,enableExpandAll: false, //hide expand all button in header});
Expand | First Name | Last Name | Address | City | State |
---|---|---|---|---|---|
Dylan | Murray | 261 Erdman Ford | East Daphne | Kentucky | |
Raquel | Kohler | 769 Dominic Grove | Columbus | Ohio |
1import { useMemo } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6} from 'material-react-table';78export type Person = {9 firstName: string;10 lastName: string;11 address: string;12 city: string;13 state: string;14 subRows?: Person[]; //Each person can have sub rows of more people15};1617export const data = [18 {19 firstName: 'Dylan',20 lastName: 'Murray',21 address: '261 Erdman Ford',22 city: 'East Daphne',23 state: 'Kentucky',24 subRows: [25 {26 firstName: 'Ervin',27 lastName: 'Reinger',28 address: '566 Brakus Inlet',29 city: 'South Linda',30 state: 'West Virginia',31 subRows: [32 {33 firstName: 'Jordane',34 lastName: 'Homenick',35 address: '1234 Brakus Inlet',36 city: 'South Linda',37 state: 'West Virginia',38 },39 ],40 },41 {42 firstName: 'Brittany',43 lastName: 'McCullough',44 address: '722 Emie Stream',45 city: 'Lincoln',46 state: 'Nebraska',47 },48 ],49 },50 {51 firstName: 'Raquel',52 lastName: 'Kohler',53 address: '769 Dominic Grove',54 city: 'Columbus',55 state: 'Ohio',56 subRows: [57 {58 firstName: 'Branson',59 lastName: 'Frami',60 address: '32188 Larkin Turnpike',61 city: 'Charleston',62 state: 'South Carolina',63 },64 ],65 },66];6768const Example = () => {69 const columns = useMemo<MRT_ColumnDef<Person>[]>(70 //column definitions...98 );99100 const table = useMaterialReactTable({101 columns,102 data,103 enableExpandAll: false, //hide expand all double arrow in column header104 enableExpanding: true,105 });106107 return <MaterialReactTable table={table} />;108};109110export default Example;111
Generate Sub Rows with getSubRows
If your data is not yet in a tree structure, but the data has relationships that can be parsed into a tree, you can use the getSubRows
table option to let TanStack Table find the sub rows for each row.
There are a couple key things you have to do to make this work:
Only pass in root (top level) rows in your data prop.
Set the
getSubRows
table option to a function that scans all the rest of your data and returns the sub rows for a given row.
NOTE: Be conscious of the performance implications of the
getSubRows
function. It will be called for every row in your table, so it should be performant.
First Name | Last Name | Email | State | |
---|---|---|---|---|
Henry | Lynch | Camden.Macejkovic@yahoo.com | California | |
Mckenna | Friesen | Veda_Feeney@yahoo.com | New York |
1import { useMemo } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6} from 'material-react-table';78export type Employee = {9 id: string;10 firstName: string;11 lastName: string;12 email: string;13 state: string;14 managerId: string | null;15};1617//flat data that TanStack Table's getSubRows() function will parse into a tree18export const data: Employee[] = [19 {20 id: '9s41rp',21 firstName: 'Kelvin',22 lastName: 'Langosh',23 email: 'Jerod14@hotmail.com',24 state: 'Ohio',25 managerId: '08m6rx',26 },27 {28 id: '08m6rx',29 firstName: 'Molly',30 lastName: 'Purdy',31 email: 'Hugh.Dach79@hotmail.com',32 state: 'Rhode Island',33 managerId: '5ymtrc',34 },35 {36 id: '5ymtrc',37 firstName: 'Henry',38 lastName: 'Lynch',39 email: 'Camden.Macejkovic@yahoo.com',40 state: 'California',41 managerId: null, //top of a tree42 },43 {44 id: 'ek5b97',45 firstName: 'Glenda',46 lastName: 'Douglas',47 email: 'Eric0@yahoo.com',48 state: 'Montana',49 managerId: '08m6rx',50 },51 {52 id: 'xxtydd',53 firstName: 'Leone',54 lastName: 'Williamson',55 email: 'Ericka_Mueller52@yahoo.com',56 state: 'Colorado',57 managerId: '08m6rx',58 },59 {60 id: 'wzxj9m',61 firstName: 'Mckenna',62 lastName: 'Friesen',63 email: 'Veda_Feeney@yahoo.com',64 state: 'New York',65 managerId: null, //top of a tree66 },67 {68 id: '21dwtz',69 firstName: 'Wyman',70 lastName: 'Jast',71 email: 'Melvin.Pacocha@yahoo.com',72 state: 'Montana',73 managerId: 'wzxj9m',74 },75 {76 id: 'o8oe4k',77 firstName: 'Janick',78 lastName: 'Willms',79 email: 'Delfina12@gmail.com',80 state: 'Nebraska',81 managerId: 'wzxj9m',82 },83];8485const Example = () => {86 const columns = useMemo<MRT_ColumnDef<Employee>[]>(87 //column definitions...110 );111112 //only root rows with no managerId113 const rootData = useMemo(() => data.filter((r) => !r.managerId), [data]);114115 const table = useMaterialReactTable({116 columns,117 data: rootData,118 enableExpanding: true,119 //note: performance of this example should be improved with hash maps. This is currently 0(n^2)120 getSubRows: (row) => data.filter((r) => r.managerId === row.id),121 });122123 return <MaterialReactTable table={table} />;124};125126export default Example;127
Expanded Rows Pagination Behavior
By default, Material React Table will treat expanded sub-rows the same as any other row when it comes to pagination. This means that some expanded rows may be on the next page. You can change this behavior by setting the paginateExpandedRows
table option to false
.
const table = useMaterialReactTable({columns,data,enableExpanding: true,paginateExpandedRows: false, //expanded rows will be on the same page as their parent row});
Expanded Leaf Row Filtering Behavior
If you are using the filtering features alongside sub-row features, then there are a few behaviors and customizations you should be aware of.
Filter From Leaf Rows
By default, filtering is done from parent rows down (so if a parent row is filtered out, all of its children will be filtered out as well). Setting the filterFromLeafRows
table option to true
will cause filtering to be done from leaf rows up (which means parent rows will be kept so long as one of their child, or grand-child, etc. rows pass the filtering).
const table = useMaterialReactTable({columns,data,enableExpanding: true,filterFromLeafRows: true, //search for child rows and preserve parent rows});
Max Leaf Row Filter Depth
By default, filtering is done for all rows (max depth of 100), no matter if they are root level parent rows or the child leaf rows of a parent row. Setting the maxLeafRowFilterDepth
table option to 0
will cause filtering to only be applied to the root level parent rows, with all sub-rows remaining unfiltered. Similarly, setting this option to 1 will cause filtering to only be applied to child leaf rows 1 level deep, and so on.
This is useful for situations where you want a row's entire child hierarchy to be visible, regardless of the applied filter.
const table = useMaterialReactTable({columns,data,enableExpanding: true,maxLeafRowFilterDepth: 0, //When filtering root rows, keep all child rows of the passing parent rows});
Expand All Rows By Default
You can manage the initial state of the expanded rows with the expanded
state option in either the initialState
or state
props.
For example, you may want all rows to be expanded by default. To do this, you can simply set the expanded
state option to true
.
const table = useMaterialReactTable({columns,data,enableExpanding: true,initialState: { expanded: true }, //all rows expanded by default});
Expand | First Name | Last Name | Address | City | State |
---|---|---|---|---|---|
Dylan | Murray | 261 Erdman Ford | East Daphne | Kentucky | |
Ervin | Reinger | 566 Brakus Inlet | South Linda | West Virginia | |
Jordane | Homenick | 1234 Brakus Inlet | South Linda | West Virginia | |
Jordan | Clarkson | 4882 Palm Rd | San Francisco | California | |
Brittany | McCullough | 722 Emie Stream | Lincoln | Nebraska | |
Raquel | Kohler | 769 Dominic Grove | Columbus | Ohio | |
Branson | Frami | 32188 Larkin Turnpike | Charleston | South Carolina |
1import { useMemo } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6} from 'material-react-table';78export type Person = {9 firstName: string;10 lastName: string;11 address: string;12 city: string;13 state: string;14 subRows?: Person[]; //Each person can have sub rows of more people15};1617export const data: Person[] = [18 {19 firstName: 'Dylan',20 lastName: 'Murray',21 address: '261 Erdman Ford',22 city: 'East Daphne',23 state: 'Kentucky',24 subRows: [25 {26 firstName: 'Ervin',27 lastName: 'Reinger',28 address: '566 Brakus Inlet',29 city: 'South Linda',30 state: 'West Virginia',31 subRows: [32 {33 firstName: 'Jordane',34 lastName: 'Homenick',35 address: '1234 Brakus Inlet',36 city: 'South Linda',37 state: 'West Virginia',38 },39 {40 firstName: 'Jordan',41 lastName: 'Clarkson',42 address: '4882 Palm Rd',43 city: 'San Francisco',44 state: 'California',45 },46 ],47 },48 {49 firstName: 'Brittany',50 lastName: 'McCullough',51 address: '722 Emie Stream',52 city: 'Lincoln',53 state: 'Nebraska',54 },55 ],56 },57 {58 firstName: 'Raquel',59 lastName: 'Kohler',60 address: '769 Dominic Grove',61 city: 'Columbus',62 state: 'Ohio',63 subRows: [64 {65 firstName: 'Branson',66 lastName: 'Frami',67 address: '32188 Larkin Turnpike',68 city: 'Charleston',69 state: 'South Carolina',70 },71 ],72 },73];7475const Example = () => {76 const columns = useMemo<MRT_ColumnDef<Person>[]>(77 //column definitions...105 );106107 const table = useMaterialReactTable({108 columns,109 data,110 enableExpandAll: false, //hide expand all double arrow in column header111 enableExpanding: true,112 filterFromLeafRows: true, //apply filtering to all rows instead of just parent rows113 initialState: { expanded: true }, //expand all rows by default114 paginateExpandedRows: false, //When rows are expanded, do not count sub-rows as number of rows on the page towards pagination115 });116117 return <MaterialReactTable table={table} />;118};119120export default Example;121
Expand Root Rows Only By Default
Here is a slightly more complex initial expanded state example where all the root rows are expanded by default, but none of the sub rows themselves are expanded by default. We just need to find all of the root row ids and set their key in the expanded
initialState
option to true
.
First Name | Last Name | Address | City | State | |
---|---|---|---|---|---|
Dylan | Murray | 261 Erdman Ford | East Daphne | Kentucky | |
Ervin | Reinger | 566 Brakus Inlet | South Linda | West Virginia | |
Brittany | McCullough | 722 Emie Stream | Lincoln | Nebraska | |
Raquel | Kohler | 769 Dominic Grove | Columbus | Ohio | |
Branson | Frami | 32188 Larkin Turnpike | Charleston | South Carolina |
1import { useMemo } from 'react';2import {3 MaterialReactTable,4 type MRT_ExpandedState,5 type MRT_ColumnDef,6 useMaterialReactTable,7} from 'material-react-table';8import { Button } from '@mui/material';910export type Person = {11 id: string;12 firstName: string;13 lastName: string;14 address: string;15 city: string;16 state: string;17 subRows?: Person[]; //Each person can have sub rows of more people18};1920//data definitions...9697const Example = () => {98 const columns = useMemo<MRT_ColumnDef<Person>[]>(99 //column definitions...127 );128129 const initialExpandedRootRows = useMemo<MRT_ExpandedState>(130 () =>131 data132 .map((originalRow) => originalRow.id) //get all the root row ids, use recursion for additional levels133 .reduce((a, v) => ({ ...a, [v]: true }), {}), //convert to an object with all the ids as keys and `true` as values134 [],135 );136137 const table = useMaterialReactTable({138 columns,139 data,140 enableExpanding: true,141 getRowId: (originalRow) => originalRow.id,142 initialState: { expanded: initialExpandedRootRows }, //only expand the root rows by default143 renderTopToolbarCustomActions: ({ table }) => (144 <Button onClick={() => table.resetExpanded()}>Reset Expanded</Button>145 ),146 });147148 return <MaterialReactTable table={table} />;149};150151export default Example;152