MRT logoMaterial React Table

On This Page

    State Management Guide

    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 store your own reference to the entire table instance wherever you need to.

    This is all optional, of course. If you do not need access to any of the 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 Props

    1
    Partial<MRT_TableState<TData>>
    Table State Management Guide
    2
    Partial<MRT_TableState<TData>>
    Table State Management Guide
    3
    MutableRefObject<MRT_TableInstance<TData> | null>

    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 prop 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 prop:

    <MaterialReactTable
    columns={columns}
    data={data}
    initialState={{
    density: 'compact', //set default density to compact
    expanded: true, //expand all rows by default
    pagination: { pageIndex: 0, pageSize: 15 }, //set different default page size
    showColumnFilters: true, //show filters by default
    sorting: [{ id: 'name', desc: false }], //sort by name ascending by default
    }}
    />

    Note: If you use both initialState and state, the state initializer in state prop will take precedence and overwrite the same state values in initialState. So just use either initialState or state, 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 prop, 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 here
    useEffect(() => {
    //do something when the pagination state changes
    }, [pagination]);
    return (
    <MaterialReactTable
    columns={columns}
    data={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
    />
    );

    Here is another full example for how you might manage the rowSelection state yourself:


    Demo

    Open StackblitzOpen Code SandboxOpen on GitHub
    DylanMurray22261 Erdman FordEast DaphneKentucky
    RaquelKohler18769 Dominic GroveColumbusOhio

    Rows per page

    1-2 of 2

    Source Code

    1import React, { useEffect, useMemo, useState } from 'react';
    2import {
    3 MaterialReactTable,
    4 type MRT_ColumnDef,
    5 type MRT_RowSelectionState,
    6} from 'material-react-table';
    7
    8const data = [
    9 {
    10 userId: '3f25309c-8fa1-470f-811e-cdb082ab9017', //we'll use this as a unique row id
    11 firstName: 'Dylan',
    12 lastName: 'Murray',
    13 age: 22,
    14 address: '261 Erdman Ford',
    15 city: 'East Daphne',
    16 state: 'Kentucky',
    17 }, //data definitions...
    28];
    29
    30const Example = () => {
    31 const columns = useMemo(
    32 //column definitions...
    61 );
    62
    63 //optionally, you can manage the row selection state yourself
    64 const [rowSelection, setRowSelection] = useState<MRT_RowSelectionState>({});
    65
    66 useEffect(() => {
    67 //do something when the row selection changes...
    68 console.info({ rowSelection });
    69 }, [rowSelection]);
    70
    71 return (
    72 <MaterialReactTable
    73 columns={columns}
    74 data={data}
    75 enableRowSelection
    76 getRowId={(row) => row.userId} //give each row a more useful id
    77 onRowSelectionChange={setRowSelection} //connect internal row selection state to your own
    78 state={{ rowSelection }} //pass our managed row selection state to the table to use
    79 />
    80 );
    81};
    82
    83export default Example;
    84

    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 functions is a bit strange, so pay attention to the example below.

    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 state
    setPagination((prevPagination) =>
    updater instanceof Function ? updater(prevPagination) : updater,
    );
    //put more code for your side effects here, guaranteed to only run once, even in React Strict Mode
    };
    return (
    <MaterialReactTable
    columns={columns}
    data={data}
    state={{ pagination }}
    onPaginationChange={handlePaginationChange}
    />
    );

    Access the Underlying Table Instance Reference

    You can store a reference to the underlying table instance by using the tableInstanceRef prop. This is a mutable ref object that will be populated with the table instance once the table has been initialized internally. This reference can be very useful, especially if you need it for components that are rendered outside of <MaterialReactTable />.

    //must initialize with null to make TS happy
    const tableInstanceRef = useRef<MRT_TableInstance<YourDataType>>(null);
    const someEventHandler = (event) => {
    console.info(tableInstanceRef.current?.getRowModel().rows); //example - get access to all page rows in the table
    console.info(tableInstanceRef.current?.getSelectedRowModel()); //example - get access to all selected rows in the table
    console.info(tableInstanceRef.current?.getState().sorting); //example - get access to the current sorting state
    };
    return (
    <div>
    <ExternalButton onClick={someEventHandler}>
    Export or Something
    </ExternalButton>
    <MaterialReactTable
    columns={columns}
    data={data}
    tableInstanceRef={tableInstanceRef}
    />
    </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 tableInstanceRef = useRef(null);
    const columns = useMemo(
    () => [
    {
    Header: 'Name',
    accessor: 'name',
    Cell: ({ cell, table }) => <span>{cell.getValue()}</span>,
    //The `table` parameter from the Cell option params and the `tableInstanceRef.current` are the same object
    },
    ],
    [],
    );
    return (
    <MaterialReactTable
    columns={columns}
    data={data}
    tableInstanceRef={tableInstanceRef}
    renderTopToolbarCustomActions={({ table }) => {
    //The `table` parameter and the `tableInstanceRef.current` are the same object
    return <Button>Button</Button>;
    }}
    />
    );

    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 prop and the on[StateName]Change callbacks. Here is an example of how you might implement persistent state using sessionStorage:


    Demo

    AllisonBrownOmahaNebraska10000
    HarrySmithHickmanNebraska20000
    SallyWilliamsonAllianceNebraska30000
    LebronJamesIndianapolisIndiana40000
    MichaelMcGinnisHarrisonburgVirginia150000
    JosephWilliamsValentineNebraska100000
    NoahBrownToledoOhio50000
    MasonZhangSacramentoCalifornia100000
    VioletDoeSan FranciscoCalifornia100000

    Rows per page

    1-9 of 9

    Source Code

    1import React, { 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';
    12
    13//column definitions...
    37
    38const Example = () => {
    39 const isFirstRender = useRef(true);
    40
    41 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>([]);
    54
    55 //load state from local storage
    56 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');
    70
    71 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 }, []);
    94
    95 //save states to local storage
    96 useEffect(() => {
    97 if (isFirstRender.current) return;
    98 sessionStorage.setItem(
    99 'mrt_columnFilters_table_1',
    100 JSON.stringify(columnFilters),
    101 );
    102 }, [columnFilters]);
    103
    104 useEffect(() => {
    105 if (isFirstRender.current) return;
    106 sessionStorage.setItem(
    107 'mrt_columnVisibility_table_1',
    108 JSON.stringify(columnVisibility),
    109 );
    110 }, [columnVisibility]);
    111
    112 useEffect(() => {
    113 if (isFirstRender.current) return;
    114 sessionStorage.setItem('mrt_density_table_1', JSON.stringify(density));
    115 }, [density]);
    116
    117 useEffect(() => {
    118 if (isFirstRender.current) return;
    119 sessionStorage.setItem(
    120 'mrt_globalFilter_table_1',
    121 JSON.stringify(globalFilter ?? ''),
    122 );
    123 }, [globalFilter]);
    124
    125 useEffect(() => {
    126 if (isFirstRender.current) return;
    127 sessionStorage.setItem(
    128 'mrt_showGlobalFilter_table_1',
    129 JSON.stringify(showGlobalFilter),
    130 );
    131 }, [showGlobalFilter]);
    132
    133 useEffect(() => {
    134 if (isFirstRender.current) return;
    135 sessionStorage.setItem(
    136 'mrt_showColumnFilters_table_1',
    137 JSON.stringify(showColumnFilters),
    138 );
    139 }, [showColumnFilters]);
    140
    141 useEffect(() => {
    142 if (isFirstRender.current) return;
    143 sessionStorage.setItem('mrt_sorting_table_1', JSON.stringify(sorting));
    144 }, [sorting]);
    145
    146 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 };
    156
    157 return (
    158 <MaterialReactTable
    159 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};
    183
    184export default Example;
    185