Editing (CRUD) Example
Full CRUD (Create, Read, Update, Delete) functionality is easily able to be implemented with the Material React Table, with a combination of editing, toolbar, and row action features.
Actions | ID | First Name | Last Name | Email | Age | State |
---|---|---|---|---|---|---|
9s41rp | Kelvin | Langosh | Jerod14@hotmail.com | 19 | Ohio | |
08m6rx | Molly | Purdy | Hugh.Dach79@hotmail.com | 37 | Rhode Island | |
5ymtrc | Henry | Lynch | Camden.Macejkovic@yahoo.com | 20 | California | |
ek5b97 | Glenda | Douglas | Eric0@yahoo.com | 38 | Montana | |
xxtydd | Leone | Williamson | Ericka_Mueller52@yahoo.com | 19 | Colorado | |
wzxj9m | Mckenna | Friesen | Veda_Feeney@yahoo.com | 34 | New York | |
21dwtz | Wyman | Jast | Melvin.Pacocha@yahoo.com | 23 | Montana | |
o8oe4k | Janick | Willms | Delfina12@gmail.com | 25 | Nebraska |
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';2324export type Person = {25 id: string;26 firstName: string;27 lastName: string;28 email: string;29 age: number;30 state: string;31};3233const 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 }>({});3940 const handleCreateNewRow = (values: Person) => {41 tableData.push(values);42 setTableData([...tableData]);43 };4445 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-render50 setTableData([...tableData]);51 exitEditingMode(); //required to exit editing mode and close modal52 }53 };5455 const handleDeleteRow = useCallback(56 (row: MRT_Row<Person>) => {57 if (58 !confirm(`Are you sure you want to delete ${row.getValue('firstName')}`)59 ) {60 return;61 }62 //send api delete request here, then refetch or update local table data for re-render63 tableData.splice(row.index, 1);64 setTableData([...tableData]);65 },66 [tableData],67 );6869 const getCommonEditTextFieldProps = useCallback(70 (71 cell: MRT_Cell<Person>,72 ): MRT_ColumnDef<Person>['muiTableBodyCellEditTextFieldProps'] => {73 return {74 error: !!validationErrors[cell.id],75 helperText: validationErrors[cell.id],76 onBlur: (event) => {77 const isValid =78 cell.column.id === 'email'79 ? validateEmail(event.target.value)80 : cell.column.id === 'age'81 ? validateAge(+event.target.value)82 : validateRequired(event.target.value);83 if (!isValid) {84 //set validation error for cell if invalid85 setValidationErrors({86 ...validationErrors,87 [cell.id]: `${cell.column.columnDef.header} is required`,88 });89 } else {90 //remove validation error for cell if valid91 delete validationErrors[cell.id];92 setValidationErrors({93 ...validationErrors,94 });95 }96 },97 };98 },99 [validationErrors],100 );101102 const columns = useMemo<MRT_ColumnDef<Person>[]>(103 () => [104 {105 accessorKey: 'id',106 header: 'ID',107 enableColumnOrdering: false,108 enableEditing: false, //disable editing on this column109 enableSorting: false,110 size: 80,111 },112 {113 accessorKey: 'firstName',114 header: 'First Name',115 size: 140,116 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({117 ...getCommonEditTextFieldProps(cell),118 }),119 },120 {121 accessorKey: 'lastName',122 header: 'Last Name',123 size: 140,124 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({125 ...getCommonEditTextFieldProps(cell),126 }),127 },128 {129 accessorKey: 'email',130 header: 'Email',131 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({132 ...getCommonEditTextFieldProps(cell),133 type: 'email',134 }),135 },136 {137 accessorKey: 'age',138 header: 'Age',139 size: 80,140 muiTableBodyCellEditTextFieldProps: ({ cell }) => ({141 ...getCommonEditTextFieldProps(cell),142 type: 'number',143 }),144 },145 {146 accessorKey: 'state',147 header: 'State',148 muiTableBodyCellEditTextFieldProps: {149 select: true, //change to select for a dropdown150 children: states.map((state) => (151 <MenuItem key={state} value={state}>152 {state}153 </MenuItem>154 )),155 },156 },157 ],158 [getCommonEditTextFieldProps],159 );160161 return (162 <>163 <MaterialReactTable164 displayColumnDefOptions={{165 'mrt-row-actions': {166 muiTableHeadCellProps: {167 align: 'center',168 },169 size: 120,170 },171 }}172 columns={columns}173 data={tableData}174 editingMode="modal" //default175 enableColumnOrdering176 enableEditing177 onEditingRowSave={handleSaveRowEdits}178 renderRowActions={({ row, table }) => (179 <Box sx={{ display: 'flex', gap: '1rem' }}>180 <Tooltip arrow placement="left" title="Edit">181 <IconButton onClick={() => table.setEditingRow(row)}>182 <Edit />183 </IconButton>184 </Tooltip>185 <Tooltip arrow placement="right" title="Delete">186 <IconButton color="error" onClick={() => handleDeleteRow(row)}>187 <Delete />188 </IconButton>189 </Tooltip>190 </Box>191 )}192 renderTopToolbarCustomActions={() => (193 <Button194 color="secondary"195 onClick={() => setCreateModalOpen(true)}196 variant="contained"197 >198 Create New Account199 </Button>200 )}201 />202 <CreateNewAccountModal203 columns={columns}204 open={createModalOpen}205 onClose={() => setCreateModalOpen(false)}206 onSubmit={handleCreateNewRow}207 />208 </>209 );210};211212//example of creating a mui dialog modal for creating new rows213export const CreateNewAccountModal: FC<{214 columns: MRT_ColumnDef<Person>[];215 onClose: () => void;216 onSubmit: (values: Person) => void;217 open: boolean;218}> = ({ open, columns, onClose, onSubmit }) => {219 const [values, setValues] = useState<any>(() =>220 columns.reduce((acc, column) => {221 acc[column.accessorKey ?? ''] = '';222 return acc;223 }, {} as any),224 );225226 const handleSubmit = () => {227 //put your validation logic here228 onSubmit(values);229 onClose();230 };231232 return (233 <Dialog open={open}>234 <DialogTitle textAlign="center">Create New Account</DialogTitle>235 <DialogContent>236 <form onSubmit={(e) => e.preventDefault()}>237 <Stack238 sx={{239 width: '100%',240 minWidth: { xs: '300px', sm: '360px', md: '400px' },241 gap: '1.5rem',242 }}243 >244 {columns.map((column) => (245 <TextField246 key={column.accessorKey}247 label={column.header}248 name={column.accessorKey}249 onChange={(e) =>250 setValues({ ...values, [e.target.name]: e.target.value })251 }252 />253 ))}254 </Stack>255 </form>256 </DialogContent>257 <DialogActions sx={{ p: '1.25rem' }}>258 <Button onClick={onClose}>Cancel</Button>259 <Button color="secondary" onClick={handleSubmit} variant="contained">260 Create New Account261 </Button>262 </DialogActions>263 </Dialog>264 );265};266267const validateRequired = (value: string) => !!value.length;268const validateEmail = (email: string) =>269 !!email.length &&270 email271 .toLowerCase()272 .match(273 /^(([^<>()[\]\\.,;:\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,}))$/,274 );275const validateAge = (age: number) => age >= 18 && age <= 50;276277export default Example;278
View Extra Storybook Examples