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