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).
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, { 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 = () => {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 handleCancelRowEdits = () => {56 setValidationErrors({});57 };5859 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-render67 tableData.splice(row.index, 1);68 setTableData([...tableData]);69 },70 [tableData],71 );7273 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 invalid89 setValidationErrors({90 ...validationErrors,91 [cell.id]: `${cell.column.columnDef.header} is required`,92 });93 } else {94 //remove validation error for cell if valid95 delete validationErrors[cell.id];96 setValidationErrors({97 ...validationErrors,98 });99 }100 },101 };102 },103 [validationErrors],104 );105106 const columns = useMemo<MRT_ColumnDef<Person>[]>(107 () => [108 {109 accessorKey: 'id',110 header: 'ID',111 enableColumnOrdering: false,112 enableEditing: false, //disable editing on this column113 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 dropdown154 children: states.map((state) => (155 <MenuItem key={state} value={state}>156 {state}157 </MenuItem>158 )),159 },160 },161 ],162 [getCommonEditTextFieldProps],163 );164165 return (166 <>167 <MaterialReactTable168 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" //default179 enableColumnOrdering180 enableEditing181 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 <Button199 color="secondary"200 onClick={() => setCreateModalOpen(true)}201 variant="contained"202 >203 Create New Account204 </Button>205 )}206 />207 <CreateNewAccountModal208 columns={columns}209 open={createModalOpen}210 onClose={() => setCreateModalOpen(false)}211 onSubmit={handleCreateNewRow}212 />213 </>214 );215};216217interface CreateModalProps {218 columns: MRT_ColumnDef<Person>[];219 onClose: () => void;220 onSubmit: (values: Person) => void;221 open: boolean;222}223224//example of creating a mui dialog modal for creating new rows225export const CreateNewAccountModal = ({226 open,227 columns,228 onClose,229 onSubmit,230}: CreateModalProps) => {231 const [values, setValues] = useState<any>(() =>232 columns.reduce((acc, column) => {233 acc[column.accessorKey ?? ''] = '';234 return acc;235 }, {} as any),236 );237238 const handleSubmit = () => {239 //put your validation logic here240 onSubmit(values);241 onClose();242 };243244 return (245 <Dialog open={open}>246 <DialogTitle textAlign="center">Create New Account</DialogTitle>247 <DialogContent>248 <form onSubmit={(e) => e.preventDefault()}>249 <Stack250 sx={{251 width: '100%',252 minWidth: { xs: '300px', sm: '360px', md: '400px' },253 gap: '1.5rem',254 }}255 >256 {columns.map((column) => (257 <TextField258 key={column.accessorKey}259 label={column.header}260 name={column.accessorKey}261 onChange={(e) =>262 setValues({ ...values, [e.target.name]: e.target.value })263 }264 />265 ))}266 </Stack>267 </form>268 </DialogContent>269 <DialogActions sx={{ p: '1.25rem' }}>270 <Button onClick={onClose}>Cancel</Button>271 <Button color="secondary" onClick={handleSubmit} variant="contained">272 Create New Account273 </Button>274 </DialogActions>275 </Dialog>276 );277};278279const validateRequired = (value: string) => !!value.length;280const validateEmail = (email: string) =>281 !!email.length &&282 email283 .toLowerCase()284 .match(285 /^(([^<>()[\]\\.,;:\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,}))$/,286 );287const validateAge = (age: number) => age >= 18 && age <= 50;288289export default Example;290
View Extra Storybook Examples