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.

More Examples

Demo

Open StackblitzOpen Code SandboxOpen on GitHub

avatarDusty Kuvalis
$52,729Chief Creative Technician3/20/2014
avatarD'angelo Moen
$71,964Forward Response Engineer3/9/2018
avatarDevan Reinger
$72,551Customer Intranet Consultant8/12/2020
avatarLeonardo Langworth
$57,801Senior Security Manager7/25/2017
avatarDouglas Denesik
$23,792Legacy Security Assistant4/12/2020
avatarJameson Mayer
$80,916Regional Division Planner10/30/2017
avatarMadaline Quitzon
$68,052Corporate Paradigm Strategist1/17/2018
avatarWilfrid Vandervort
$85,573Legacy Functionality Specialist8/4/2014
avatarChelsie Mraz
$51,062Forward Infrastructure Representative1/6/2021
avatarHassie Bruen
$61,196Human Paradigm Designer4/28/2016

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,
146 enableColumnFilterModes: true,
147 enableColumnOrdering: true,
148 enableGrouping: true,
149 enableColumnPinning: true,
150 enableFacetedValues: true,
151 enableRowActions: true,
152 enableRowSelection: true,
153 initialState: { showColumnFilters: true, showGlobalFilter: true },
154 paginationDisplayMode: 'pages',
155 positionToolbarAlertBanner: 'bottom',
156 muiSearchTextFieldProps: {
157 size: 'small',
158 variant: 'outlined',
159 },
160 muiPaginationProps: {
161 color: 'secondary',
162 rowsPerPageOptions: [10, 20, 30],
163 shape: 'rounded',
164 variant: 'outlined',
165 },
166 renderDetailPanel: ({ row }) => (
167 <Box
168 sx={{
169 display: 'flex',
170 justifyContent: 'space-around',
171 alignItems: 'center',
172 }}
173 >
174 <img
175 alt="avatar"
176 height={200}
177 src={row.original.avatar}
178 loading="lazy"
179 style={{ borderRadius: '50%' }}
180 />
181 <Box sx={{ textAlign: 'center' }}>
182 <Typography variant="h4">Signature Catch Phrase:</Typography>
183 <Typography variant="h1">
184 &quot;{row.original.signatureCatchPhrase}&quot;
185 </Typography>
186 </Box>
187 </Box>
188 ),
189 renderRowActionMenuItems: ({ closeMenu }) => [
190 <MenuItem
191 key={0}
192 onClick={() => {
193 // View profile logic...
194 closeMenu();
195 }}
196 sx={{ m: 0 }}
197 >
198 <ListItemIcon>
199 <AccountCircle />
200 </ListItemIcon>
201 View Profile
202 </MenuItem>,
203 <MenuItem
204 key={1}
205 onClick={() => {
206 // Send email logic...
207 closeMenu();
208 }}
209 sx={{ m: 0 }}
210 >
211 <ListItemIcon>
212 <Send />
213 </ListItemIcon>
214 Send Email
215 </MenuItem>,
216 ],
217 renderTopToolbar: ({ table }) => {
218 const handleDeactivate = () => {
219 table.getSelectedRowModel().flatRows.map((row) => {
220 alert('deactivating ' + row.getValue('name'));
221 });
222 };
223
224 const handleActivate = () => {
225 table.getSelectedRowModel().flatRows.map((row) => {
226 alert('activating ' + row.getValue('name'));
227 });
228 };
229
230 const handleContact = () => {
231 table.getSelectedRowModel().flatRows.map((row) => {
232 alert('contact ' + row.getValue('name'));
233 });
234 };
235
236 return (
237 <Box
238 sx={(theme) => ({
239 backgroundColor: lighten(theme.palette.background.default, 0.05),
240 display: 'flex',
241 gap: '0.5rem',
242 p: '8px',
243 justifyContent: 'space-between',
244 })}
245 >
246 <Box sx={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
247 {/* import MRT sub-components */}
248 <MRT_GlobalFilterTextField table={table} />
249 <MRT_ToggleFiltersButton table={table} />
250 </Box>
251 <Box>
252 <Box sx={{ display: 'flex', gap: '0.5rem' }}>
253 <Button
254 color="error"
255 disabled={!table.getIsSomeRowsSelected()}
256 onClick={handleDeactivate}
257 variant="contained"
258 >
259 Deactivate
260 </Button>
261 <Button
262 color="success"
263 disabled={!table.getIsSomeRowsSelected()}
264 onClick={handleActivate}
265 variant="contained"
266 >
267 Activate
268 </Button>
269 <Button
270 color="info"
271 disabled={!table.getIsSomeRowsSelected()}
272 onClick={handleContact}
273 variant="contained"
274 >
275 Contact
276 </Button>
277 </Box>
278 </Box>
279 </Box>
280 );
281 },
282 });
283
284 return <MaterialReactTable table={table} />;
285};
286
287//Date Picker Imports - these should just be in your Context Provider
288import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
289import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
290
291const ExampleWithLocalizationProvider = () => (
292 //App.tsx or AppProviders file
293 <LocalizationProvider dateAdapter={AdapterDayjs}>
294 <Example />
295 </LocalizationProvider>
296);
297
298export default ExampleWithLocalizationProvider;
299

View Extra Storybook Examples