MRT logoMaterial React Table

Advanced Example

Here is a more advanced example showcasing Material React Table's many features. Features such as row selection, expanding detail panels, header groups, column ordering, column pinning, column grouping, custom column and cell renders, etc., can be seen here.

This example is still only using client-side features. If you want to see an example of how to use Material React Table with server side logic and remote data, check out either the Remote Data Example or the React-Query Example.

Data Export
DnD
Editing
Filtering
Fetching
Pinning
Virtualization
More Examples

Demo

Open StackblitzOpen Code SandboxOpen on GitHub

avatarDusty Kuvalis
$52,7293/20/2014Chief Creative Technician
avatarD'angelo Moen
$71,9643/9/2018Forward Response Engineer
avatarDevan Reinger
$72,5518/12/2020Customer Intranet Consultant
avatarLeonardo Langworth
$57,8017/25/2017Senior Security Manager
avatarDouglas Denesik
$23,7924/12/2020Legacy Security Assistant
avatarJameson Mayer
$80,91610/30/2017Regional Division Planner
avatarMadaline Quitzon
$68,0521/17/2018Corporate Paradigm Strategist
avatarWilfrid Vandervort
$85,5738/4/2014Legacy Functionality Specialist
avatarChelsie Mraz
$51,0621/6/2021Forward Infrastructure Representative
avatarHassie Bruen
$61,1964/28/2016Human Paradigm Designer

Source Code

1import { useMemo } from 'react';
2
3//MRT Imports
4import {
5 MaterialReactTable,
6 useMaterialReactTable,
7 type MRT_ColumnDef,
8 MRT_GlobalFilterTextField,
9 MRT_ToggleFiltersButton,
10} from 'material-react-table';
11
12//Material UI Imports
13import {
14 Box,
15 Button,
16 ListItemIcon,
17 MenuItem,
18 Typography,
19 lighten,
20} from '@mui/material';
21
22//Icons Imports
23import { AccountCircle, Send } from '@mui/icons-material';
24
25//Mock Data
26import { data } from './makeData';
27
28export type Employee = {
29 firstName: string;
30 lastName: string;
31 email: string;
32 jobTitle: string;
33 salary: number;
34 startDate: string;
35 signatureCatchPhrase: string;
36 avatar: string;
37};
38
39const Example = () => {
40 const columns = useMemo<MRT_ColumnDef<Employee>[]>(
41 () => [
42 {
43 id: 'employee', //id used to define `group` column
44 header: 'Employee',
45 columns: [
46 {
47 accessorFn: (row) => `${row.firstName} ${row.lastName}`, //accessorFn used to join multiple data into a single cell
48 id: 'name', //id is still required when using accessorFn instead of accessorKey
49 header: 'Name',
50 size: 250,
51 Cell: ({ renderedCellValue, row }) => (
52 <Box
53 sx={{
54 display: 'flex',
55 alignItems: 'center',
56 gap: '1rem',
57 }}
58 >
59 <img
60 alt="avatar"
61 height={30}
62 src={row.original.avatar}
63 loading="lazy"
64 style={{ borderRadius: '50%' }}
65 />
66 {/* using renderedCellValue instead of cell.getValue() preserves filter match highlighting */}
67 <span>{renderedCellValue}</span>
68 </Box>
69 ),
70 },
71 {
72 accessorKey: 'email', //accessorKey used to define `data` column. `id` gets set to accessorKey automatically
73 enableClickToCopy: true,
74 filterVariant: 'autocomplete',
75 header: 'Email',
76 size: 300,
77 },
78 ],
79 },
80 {
81 id: 'id',
82 header: 'Job Info',
83 columns: [
84 {
85 accessorKey: 'salary',
86 // filterVariant: 'range', //if not using filter modes feature, use this instead of filterFn
87 filterFn: 'between',
88 header: 'Salary',
89 size: 200,
90 //custom conditional format and styling
91 Cell: ({ cell }) => (
92 <Box
93 component="span"
94 sx={(theme) => ({
95 backgroundColor:
96 cell.getValue<number>() < 50_000
97 ? theme.palette.error.dark
98 : cell.getValue<number>() >= 50_000 &&
99 cell.getValue<number>() < 75_000
100 ? theme.palette.warning.dark
101 : theme.palette.success.dark,
102 borderRadius: '0.25rem',
103 color: '#fff',
104 maxWidth: '9ch',
105 p: '0.25rem',
106 })}
107 >
108 {cell.getValue<number>()?.toLocaleString?.('en-US', {
109 style: 'currency',
110 currency: 'USD',
111 minimumFractionDigits: 0,
112 maximumFractionDigits: 0,
113 })}
114 </Box>
115 ),
116 },
117 {
118 accessorKey: 'jobTitle', //hey a simple column for once
119 header: 'Job Title',
120 size: 350,
121 },
122 {
123 accessorFn: (row) => new Date(row.startDate), //convert to Date for sorting and filtering
124 id: 'startDate',
125 header: 'Start Date',
126 filterVariant: 'date',
127 filterFn: 'lessThan',
128 sortingFn: 'datetime',
129 Cell: ({ cell }) => cell.getValue<Date>()?.toLocaleDateString(), //render Date as a string
130 Header: ({ column }) => <em>{column.columnDef.header}</em>, //custom header markup
131 muiFilterTextFieldProps: {
132 sx: {
133 minWidth: '250px',
134 },
135 },
136 },
137 ],
138 },
139 ],
140 [],
141 );
142
143 const table = useMaterialReactTable({
144 columns,
145 data, //data must be memoized or stable (useState, useMemo, defined outside of this component, etc.)
146 enableColumnFilterModes: true,
147 enableColumnOrdering: true,
148 enableGrouping: true,
149 enableColumnPinning: true,
150 enableFacetedValues: true,
151 enableRowActions: true,
152 enableRowSelection: true,
153 initialState: {
154 showColumnFilters: true,
155 showGlobalFilter: true,
156 columnPinning: { left: ['email'], right: ['jobTitle'] },
157 rowSelection: { 1: true },
158 },
159 paginationDisplayMode: 'pages',
160 positionToolbarAlertBanner: 'bottom',
161 muiSearchTextFieldProps: {
162 size: 'small',
163 variant: 'outlined',
164 },
165 muiPaginationProps: {
166 color: 'secondary',
167 rowsPerPageOptions: [10, 20, 30],
168 shape: 'rounded',
169 variant: 'outlined',
170 },
171 renderDetailPanel: ({ row }) => (
172 <Box
173 sx={{
174 display: 'flex',
175 justifyContent: 'space-around',
176 alignItems: 'center',
177 }}
178 >
179 <img
180 alt="avatar"
181 height={200}
182 src={row.original.avatar}
183 loading="lazy"
184 style={{ borderRadius: '50%' }}
185 />
186 <Box sx={{ textAlign: 'center' }}>
187 <Typography variant="h4">Signature Catch Phrase:</Typography>
188 <Typography variant="h1">
189 &quot;{row.original.signatureCatchPhrase}&quot;
190 </Typography>
191 </Box>
192 </Box>
193 ),
194 renderRowActionMenuItems: ({ closeMenu }) => [
195 <MenuItem
196 key={0}
197 onClick={() => {
198 // View profile logic...
199 closeMenu();
200 }}
201 sx={{ m: 0 }}
202 >
203 <ListItemIcon>
204 <AccountCircle />
205 </ListItemIcon>
206 View Profile
207 </MenuItem>,
208 <MenuItem
209 key={1}
210 onClick={() => {
211 // Send email logic...
212 closeMenu();
213 }}
214 sx={{ m: 0 }}
215 >
216 <ListItemIcon>
217 <Send />
218 </ListItemIcon>
219 Send Email
220 </MenuItem>,
221 ],
222 renderTopToolbar: ({ table }) => {
223 const handleDeactivate = () => {
224 table.getSelectedRowModel().flatRows.map((row) => {
225 alert('deactivating ' + row.getValue('name'));
226 });
227 };
228
229 const handleActivate = () => {
230 table.getSelectedRowModel().flatRows.map((row) => {
231 alert('activating ' + row.getValue('name'));
232 });
233 };
234
235 const handleContact = () => {
236 table.getSelectedRowModel().flatRows.map((row) => {
237 alert('contact ' + row.getValue('name'));
238 });
239 };
240
241 return (
242 <Box
243 sx={(theme) => ({
244 backgroundColor: lighten(theme.palette.background.default, 0.05),
245 display: 'flex',
246 gap: '0.5rem',
247 p: '8px',
248 justifyContent: 'space-between',
249 })}
250 >
251 <Box sx={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
252 {/* import MRT sub-components */}
253 <MRT_GlobalFilterTextField table={table} />
254 <MRT_ToggleFiltersButton table={table} />
255 </Box>
256 <Box>
257 <Box sx={{ display: 'flex', gap: '0.5rem' }}>
258 <Button
259 color="error"
260 disabled={!table.getIsSomeRowsSelected()}
261 onClick={handleDeactivate}
262 variant="contained"
263 >
264 Deactivate
265 </Button>
266 <Button
267 color="success"
268 disabled={!table.getIsSomeRowsSelected()}
269 onClick={handleActivate}
270 variant="contained"
271 >
272 Activate
273 </Button>
274 <Button
275 color="info"
276 disabled={!table.getIsSomeRowsSelected()}
277 onClick={handleContact}
278 variant="contained"
279 >
280 Contact
281 </Button>
282 </Box>
283 </Box>
284 </Box>
285 );
286 },
287 });
288
289 return <MaterialReactTable table={table} />;
290};
291
292//Date Picker Imports - these should just be in your Context Provider
293import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
294import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
295
296const ExampleWithLocalizationProvider = () => (
297 //App.tsx or AppProviders file
298 <LocalizationProvider dateAdapter={AdapterDayjs}>
299 <Example />
300 </LocalizationProvider>
301);
302
303export default ExampleWithLocalizationProvider;
304

View Extra Storybook Examples