State Management Guide
Note:
tableInstanceRef
has been removed in MRT v2 since theuseMaterialReactTable
hook now returns the table instance directly. See the Migration Guide for more information.
Material React Table does not try to hide any of its internal state from you. You can initialize state with custom initial values, manage individual states yourself as you discover the need to have access to them, or read any of the state from the table instance itself.
This is all optional, of course. If you do not need access to any of the internal state, you do not need to do anything and it will just automatically be managed internally.
See the State Options API Docs for more information on which states are available for you to manage.
Relevant Table Options
# | Prop Name | Type | Default Value | More Info Links | |
---|---|---|---|---|---|
1 |
| Table State Management Guide | |||
2 |
| Table State Management Guide | |||
Populate Initial State
If all you care about is setting parts of the initial or default state when the table mounts, then you may be able to specify that state in the initialState
table option and not have to worry about managing the state yourself.
For example, let's say you do not need access to the showColumnFilters
state, but you want to set the default value to true
when the table mounts. You can do that with the initialState
table option:
const table = useMaterialReactTable({columns,data,initialState: {density: 'xs', //set default density to compactexpanded: true, //expand all rows by defaultpagination: { pageIndex: 0, pageSize: 15 }, //set different default page sizeshowColumnFilters: true, //show filters by defaultsorting: [{ id: 'name', desc: false }], //sort by name ascending by default},});return <MaterialReactTable table={table} />;
Note: If you use both
initialState
andstate
, the state initializer instate
table option will take precedence and overwrite the same state values ininitialState
. So just use eitherinitialState
orstate
, not both for the same states.
Manage Individual States as Needed
It is pretty common to need to manage certain state yourself, so that you can react to changes in that state, or have easy access to it when sending it to an API.
You can pass in any state that you are managing yourself to the state
table option, and it will be used instead of the internal state. Each state property option also has a corresponding on[StateName]Change
callback that you can use set/update your managed state as it changes internally in the table.
For example, let's say you need to store the pagination, sorting, and row selection states in a place where you can easily access it in order to use it in parameters for an API call.
const [pagination, setPagination] = useState({pageIndex: 0,pageSize: 15, //set different default page size by initializing the state here});const [rowSelection, setRowSelection] = useState({});const [sorting, setSorting] = useState([{ id: 'name', desc: false }]);//see example at bottom of page for alternatives to useEffect hereuseEffect(() => {//do something when the pagination state changes}, [pagination]);const table = useMaterialReactTable({columns,data,getRowId: (originalRow) => row.username,onPaginationChange: setPagination,onRowSelectionChange: setRowSelection,onSortingChange: setSorting,state: { pagination, rowSelection, sorting }, //must pass states back down if using their on[StateName]Change callbacks});return <MaterialReactTable table={table} />;
Add Side Effects in Set State Callbacks
In React 18 and beyond, it is becoming more discouraged to use useEffect
to react to state changes, because in React Strict Mode (and maybe future versions of React), the useEffect hook may run twice per render. Instead, more event driven functions are recommended to be used. Here is an example for how that looks here. The callback signature for the on[StateName]Change
works just like a React setState callback from the useState
hook. This means that you have to check if the updater is a function or not, and then call the setState function with the updater callback if it is a function.
const [pagination, setPagination] = useState({pageIndex: 0,pageSize: 15,});const handlePaginationChange = (updater: MRT_Updater<PaginationState>) => {//call the setState as normal, but need to check if using an updater callback with a previous statesetPagination((prevPagination) =>//if updater is a function, call it with the previous state, otherwise just use the updater valueupdater instanceof Function ? updater(prevPagination) : updater,);//put more code for your side effects here, guaranteed to only run once, even in React Strict Mode};const table = useMaterialReactTable({columns,data,onPaginationChange: handlePaginationChange,state: { pagination },});return <MaterialReactTable table={table} />;
Read From the Table Instance
Note: Previously, in early MRT v1, you could use the
tableInstanceRef
table option to get access to the table instance. This is no longer necessary as theuseMaterialReactTable
hook now just returns the table instance directly.
The useMaterialReactTable
hook returns the table instance. The <MaterialReactTable />
needs the table instance for all of its internal logic, but you can also use it for your own purposes.
const table = useMaterialReactTable({columns,data,//...});const someEventHandler = (event) => {console.info(table.getRowModel().rows); //example - get access to all page rows in the tableconsole.info(table.getSelectedRowModel()); //example - get access to all selected rows in the tableconsole.info(table.getState().sorting); //example - get access to the current sorting state without having to manage it yourself};return (<div><ExternalButton onClick={someEventHandler}>Export or Something</ExternalButton><MaterialReactTable table={table} /></div>);
The table instance is the same object that you will also see as a provided parameter in many of the other callback functions throughout Material React Table, such as all the render...
props or the Cell
or Header
render overrides in the column definition options.
const columns = useMemo(() => [{Header: 'Name',accessor: 'name',Cell: ({ cell, table }) => <span>{cell.getValue()}</span>,//The `table` parameter from the Cell option params and the `table` are the same object},],[],);const table = useMaterialReactTable({columns,data,renderTopToolbarCustomActions: ({ table }) => {//The `table` parameter here and the table returned from the hook are the same objectreturn <Button>Button</Button>;},});return <MaterialReactTable table={table} />;
Persistent State
Persistent state is not a built-in feature of Material React Table, but it is an easy feature to implement yourself using the above patterns with the state
table option and the on[StateName]Change
callbacks. Here is an example of how you might implement persistent state using sessionStorage
:
First Name | Last Name | City | State | Salary |
---|---|---|---|---|
Allison | Brown | Omaha | Nebraska | 10000 |
Harry | Smith | Hickman | Nebraska | 20000 |
Sally | Williamson | Alliance | Nebraska | 30000 |
Lebron | James | Indianapolis | Indiana | 40000 |
Michael | McGinnis | Harrisonburg | Virginia | 150000 |
Joseph | Williams | Valentine | Nebraska | 100000 |
Noah | Brown | Toledo | Ohio | 50000 |
Mason | Zhang | Sacramento | California | 100000 |
Violet | Doe | San Francisco | California | 100000 |
1import { useEffect, useRef, useState } from 'react';2import { Button } from '@mui/material';3import {4 MaterialReactTable,5 type MRT_ColumnDef,6 type MRT_ColumnFiltersState,7 type MRT_DensityState,8 type MRT_SortingState,9 type MRT_VisibilityState,10} from 'material-react-table';11import { data, type Person } from './makeData';1213//column definitions...3738const Example = () => {39 const isFirstRender = useRef(true);4041 const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(42 [],43 );44 const [columnVisibility, setColumnVisibility] = useState<MRT_VisibilityState>(45 {},46 );47 const [density, setDensity] = useState<MRT_DensityState>('comfortable');48 const [globalFilter, setGlobalFilter] = useState<string | undefined>(49 undefined,50 );51 const [showGlobalFilter, setShowGlobalFilter] = useState(false);52 const [showColumnFilters, setShowColumnFilters] = useState(false);53 const [sorting, setSorting] = useState<MRT_SortingState>([]);5455 //load state from local storage56 useEffect(() => {57 const columnFilters = sessionStorage.getItem('mrt_columnFilters_table_1');58 const columnVisibility = sessionStorage.getItem(59 'mrt_columnVisibility_table_1',60 );61 const density = sessionStorage.getItem('mrt_density_table_1');62 const globalFilter = sessionStorage.getItem('mrt_globalFilter_table_1');63 const showGlobalFilter = sessionStorage.getItem(64 'mrt_showGlobalFilter_table_1',65 );66 const showColumnFilters = sessionStorage.getItem(67 'mrt_showColumnFilters_table_1',68 );69 const sorting = sessionStorage.getItem('mrt_sorting_table_1');7071 if (columnFilters) {72 setColumnFilters(JSON.parse(columnFilters));73 }74 if (columnVisibility) {75 setColumnVisibility(JSON.parse(columnVisibility));76 }77 if (density) {78 setDensity(JSON.parse(density));79 }80 if (globalFilter) {81 setGlobalFilter(JSON.parse(globalFilter) || undefined);82 }83 if (showGlobalFilter) {84 setShowGlobalFilter(JSON.parse(showGlobalFilter));85 }86 if (showColumnFilters) {87 setShowColumnFilters(JSON.parse(showColumnFilters));88 }89 if (sorting) {90 setSorting(JSON.parse(sorting));91 }92 isFirstRender.current = false;93 }, []);9495 //save states to local storage96 useEffect(() => {97 if (isFirstRender.current) return;98 sessionStorage.setItem(99 'mrt_columnFilters_table_1',100 JSON.stringify(columnFilters),101 );102 }, [columnFilters]);103104 useEffect(() => {105 if (isFirstRender.current) return;106 sessionStorage.setItem(107 'mrt_columnVisibility_table_1',108 JSON.stringify(columnVisibility),109 );110 }, [columnVisibility]);111112 useEffect(() => {113 if (isFirstRender.current) return;114 sessionStorage.setItem('mrt_density_table_1', JSON.stringify(density));115 }, [density]);116117 useEffect(() => {118 if (isFirstRender.current) return;119 sessionStorage.setItem(120 'mrt_globalFilter_table_1',121 JSON.stringify(globalFilter ?? ''),122 );123 }, [globalFilter]);124125 useEffect(() => {126 if (isFirstRender.current) return;127 sessionStorage.setItem(128 'mrt_showGlobalFilter_table_1',129 JSON.stringify(showGlobalFilter),130 );131 }, [showGlobalFilter]);132133 useEffect(() => {134 if (isFirstRender.current) return;135 sessionStorage.setItem(136 'mrt_showColumnFilters_table_1',137 JSON.stringify(showColumnFilters),138 );139 }, [showColumnFilters]);140141 useEffect(() => {142 if (isFirstRender.current) return;143 sessionStorage.setItem('mrt_sorting_table_1', JSON.stringify(sorting));144 }, [sorting]);145146 const resetState = () => {147 sessionStorage.removeItem('mrt_columnFilters_table_1');148 sessionStorage.removeItem('mrt_columnVisibility_table_1');149 sessionStorage.removeItem('mrt_density_table_1');150 sessionStorage.removeItem('mrt_globalFilter_table_1');151 sessionStorage.removeItem('mrt_showGlobalFilter_table_1');152 sessionStorage.removeItem('mrt_showColumnFilters_table_1');153 sessionStorage.removeItem('mrt_sorting_table_1');154 window.location.reload();155 };156157 return (158 <MaterialReactTable159 columns={columns}160 data={data}161 onColumnFiltersChange={setColumnFilters}162 onColumnVisibilityChange={setColumnVisibility}163 onDensityChange={setDensity}164 onGlobalFilterChange={setGlobalFilter}165 onShowColumnFiltersChange={setShowColumnFilters}166 onShowGlobalFilterChange={setShowGlobalFilter}167 onSortingChange={setSorting}168 state={{169 columnFilters,170 columnVisibility,171 density,172 globalFilter,173 showColumnFilters,174 showGlobalFilter,175 sorting,176 }}177 renderTopToolbarCustomActions={() => (178 <Button onClick={resetState}>Reset State</Button>179 )}180 />181 );182};183184export default Example;185