MRT logoMaterial React Table

Legacy V1 Docs

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.


Demo

Open StackblitzOpen Code SandboxOpen on GitHub

Filter Mode: Less Than

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

Rows per page

1-10 of 128

Source Code

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

View Extra Storybook Examples