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.


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

View Extra Storybook Examples