MRT logoMaterial React Table

Editing (CRUD) Example

Full CRUD (Create, Read, Update, Delete) functionality can be easily implemented with Material React Table, with a combination of editing, toolbar, and row action features.

This example below uses the default "modal" editing mode, where a dialog opens up to edit 1 row at a time. However be sure to check out the other editing modes and see if they fit your use case better. Other editing modes include "row" (1 row inline at a time), "cell" (1 cell inline at time), and "table" (all cells always editable).


Demo

Open StackblitzOpen Code SandboxOpen on GitHub
9s41rpKelvinLangoshJerod14@hotmail.com19Ohio
08m6rxMollyPurdyHugh.Dach79@hotmail.com37Rhode Island
5ymtrcHenryLynchCamden.Macejkovic@yahoo.com20California
ek5b97GlendaDouglasEric0@yahoo.com38Montana
xxtyddLeoneWilliamsonEricka_Mueller52@yahoo.com19Colorado
wzxj9mMckennaFriesenVeda_Feeney@yahoo.com34New York
21dwtzWymanJastMelvin.Pacocha@yahoo.com23Montana
o8oe4kJanickWillmsDelfina12@gmail.com25Nebraska

Rows per page

1-8 of 8

Source Code

1import React, { FC, useCallback, useMemo, useState } from 'react';
2import MaterialReactTable, {
3 MaterialReactTableProps,
4 MRT_Cell,
5 MRT_ColumnDef,
6 MRT_Row,
7} from 'material-react-table';
8import {
9 Box,
10 Button,
11 Dialog,
12 DialogActions,
13 DialogContent,
14 DialogTitle,
15 IconButton,
16 MenuItem,
17 Stack,
18 TextField,
19 Tooltip,
20} from '@mui/material';
21import { Delete, Edit } from '@mui/icons-material';
22import { data, states } from './makeData';
23
24export type Person = {
25 id: string;
26 firstName: string;
27 lastName: string;
28 email: string;
29 age: number;
30 state: string;
31};
32
33const Example: FC = () => {
34 const [createModalOpen, setCreateModalOpen] = useState(false);
35 const [tableData, setTableData] = useState<Person[]>(() => data);
36 const [validationErrors, setValidationErrors] = useState<{
37 [cellId: string]: string;
38 }>({});
39
40 const handleCreateNewRow = (values: Person) => {
41 tableData.push(values);
42 setTableData([...tableData]);
43 };
44
45 const handleSaveRowEdits: MaterialReactTableProps<Person>['onEditingRowSave'] =
46 async ({ exitEditingMode, row, values }) => {
47 if (!Object.keys(validationErrors).length) {
48 tableData[row.index] = values;
49 //send/receive api updates here, then refetch or update local table data for re-render
50 setTableData([...tableData]);
51 exitEditingMode(); //required to exit editing mode and close modal
52 }
53 };
54
55 const handleCancelRowEdits = () => {
56 setValidationErrors({});
57 };
58
59 const handleDeleteRow = useCallback(
60 (row: MRT_Row<Person>) => {
61 if (
62 !confirm(`Are you sure you want to delete ${row.getValue('firstName')}`)
63 ) {
64 return;
65 }
66 //send api delete request here, then refetch or update local table data for re-render
67 tableData.splice(row.index, 1);
68 setTableData([...tableData]);
69 },
70 [tableData],
71 );
72
73 const getCommonEditTextFieldProps = useCallback(
74 (
75 cell: MRT_Cell<Person>,
76 ): MRT_ColumnDef<Person>['muiTableBodyCellEditTextFieldProps'] => {
77 return {
78 error: !!validationErrors[cell.id],
79 helperText: validationErrors[cell.id],
80 onBlur: (event) => {
81 const isValid =
82 cell.column.id === 'email'
83 ? validateEmail(event.target.value)
84 : cell.column.id === 'age'
85 ? validateAge(+event.target.value)
86 : validateRequired(event.target.value);
87 if (!isValid) {
88 //set validation error for cell if invalid
89 setValidationErrors({
90 ...validationErrors,
91 [cell.id]: `${cell.column.columnDef.header} is required`,
92 });
93 } else {
94 //remove validation error for cell if valid
95 delete validationErrors[cell.id];
96 setValidationErrors({
97 ...validationErrors,
98 });
99 }
100 },
101 };
102 },
103 [validationErrors],
104 );
105
106 const columns = useMemo<MRT_ColumnDef<Person>[]>(
107 () => [
108 {
109 accessorKey: 'id',
110 header: 'ID',
111 enableColumnOrdering: false,
112 enableEditing: false, //disable editing on this column
113 enableSorting: false,
114 size: 80,
115 },
116 {
117 accessorKey: 'firstName',
118 header: 'First Name',
119 size: 140,
120 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({
121 ...getCommonEditTextFieldProps(cell),
122 }),
123 },
124 {
125 accessorKey: 'lastName',
126 header: 'Last Name',
127 size: 140,
128 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({
129 ...getCommonEditTextFieldProps(cell),
130 }),
131 },
132 {
133 accessorKey: 'email',
134 header: 'Email',
135 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({
136 ...getCommonEditTextFieldProps(cell),
137 type: 'email',
138 }),
139 },
140 {
141 accessorKey: 'age',
142 header: 'Age',
143 size: 80,
144 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({
145 ...getCommonEditTextFieldProps(cell),
146 type: 'number',
147 }),
148 },
149 {
150 accessorKey: 'state',
151 header: 'State',
152 muiTableBodyCellEditTextFieldProps: {
153 select: true, //change to select for a dropdown
154 children: states.map((state) => (
155 <MenuItem key={state} value={state}>
156 {state}
157 </MenuItem>
158 )),
159 },
160 },
161 ],
162 [getCommonEditTextFieldProps],
163 );
164
165 return (
166 <>
167 <MaterialReactTable
168 displayColumnDefOptions={{
169 'mrt-row-actions': {
170 muiTableHeadCellProps: {
171 align: 'center',
172 },
173 size: 120,
174 },
175 }}
176 columns={columns}
177 data={tableData}
178 editingMode="modal" //default
179 enableColumnOrdering
180 enableEditing
181 onEditingRowSave={handleSaveRowEdits}
182 onEditingRowCancel={handleCancelRowEdits}
183 renderRowActions={({ row, table }) => (
184 <Box sx={{ display: 'flex', gap: '1rem' }}>
185 <Tooltip arrow placement="left" title="Edit">
186 <IconButton onClick={() => table.setEditingRow(row)}>
187 <Edit />
188 </IconButton>
189 </Tooltip>
190 <Tooltip arrow placement="right" title="Delete">
191 <IconButton color="error" onClick={() => handleDeleteRow(row)}>
192 <Delete />
193 </IconButton>
194 </Tooltip>
195 </Box>
196 )}
197 renderTopToolbarCustomActions={() => (
198 <Button
199 color="secondary"
200 onClick={() => setCreateModalOpen(true)}
201 variant="contained"
202 >
203 Create New Account
204 </Button>
205 )}
206 />
207 <CreateNewAccountModal
208 columns={columns}
209 open={createModalOpen}
210 onClose={() => setCreateModalOpen(false)}
211 onSubmit={handleCreateNewRow}
212 />
213 </>
214 );
215};
216
217//example of creating a mui dialog modal for creating new rows
218export const CreateNewAccountModal: FC<{
219 columns: MRT_ColumnDef<Person>[];
220 onClose: () => void;
221 onSubmit: (values: Person) => void;
222 open: boolean;
223}> = ({ open, columns, onClose, onSubmit }) => {
224 const [values, setValues] = useState<any>(() =>
225 columns.reduce((acc, column) => {
226 acc[column.accessorKey ?? ''] = '';
227 return acc;
228 }, {} as any),
229 );
230
231 const handleSubmit = () => {
232 //put your validation logic here
233 onSubmit(values);
234 onClose();
235 };
236
237 return (
238 <Dialog open={open}>
239 <DialogTitle textAlign="center">Create New Account</DialogTitle>
240 <DialogContent>
241 <form onSubmit={(e) => e.preventDefault()}>
242 <Stack
243 sx={{
244 width: '100%',
245 minWidth: { xs: '300px', sm: '360px', md: '400px' },
246 gap: '1.5rem',
247 }}
248 >
249 {columns.map((column) => (
250 <TextField
251 key={column.accessorKey}
252 label={column.header}
253 name={column.accessorKey}
254 onChange={(e) =>
255 setValues({ ...values, [e.target.name]: e.target.value })
256 }
257 />
258 ))}
259 </Stack>
260 </form>
261 </DialogContent>
262 <DialogActions sx={{ p: '1.25rem' }}>
263 <Button onClick={onClose}>Cancel</Button>
264 <Button color="secondary" onClick={handleSubmit} variant="contained">
265 Create New Account
266 </Button>
267 </DialogActions>
268 </Dialog>
269 );
270};
271
272const validateRequired = (value: string) => !!value.length;
273const validateEmail = (email: string) =>
274 !!email.length &&
275 email
276 .toLowerCase()
277 .match(
278 /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
279 );
280const validateAge = (age: number) => age >= 18 && age <= 50;
281
282export default Example;
283

View Extra Storybook Examples