Data Grid - Editing
The data grid has built-in support for cell and row editing.
⚠️ This page refers to the new editing API, which is not enabled by default. To use it, add the following flag:
<DataGrid experimentalFeatures={{ newEditingApi: true }} />
This additional step is required because the default editing API has a couple of issues that can only be fixed with breaking changes, that will only be possible in v6. To avoid having to wait for the next major release window, all breaking changes needed were included inside this flag.
If you are looking for the documentation for the default editing API, visit this page. Note that it is encouraged to migrate to the new editing API since it will be enabled by default in v6. Although it says "experimental," you can consider it stable.
Making a column editable
You can make a column editable by enabling the editable
property in its column definition:
<DataGrid columns={[{ field: 'name', editable: true }]} />
This lets the user edit any cell from the specified column.
By default, only one cell at a time can have its editMode
prop set to "edit"
.
To let your users edit all cells in a given row simultaneously, set the editMode
prop to "row"
.
For more information, see the section on row editing.
The following demo shows an example of how to make all columns editable. Play with it by double-clicking or pressing Enter in any cell from this column:
Start editing
Users can start editing a cell (or row if editMode="row"
) with any of the following actions:
Double-clicking a cell
Pressing Enter, Backspace or Delete—note that the latter two options both delete any existing content
Pressing any printable key, such as a, E, 0, or $
Calling
apiRef.current.startCellEditMode
passing the row ID and column field of the cell to be editedapiRef.current.startCellEditMode({ id: 1, field: 'name' });
Calling
apiRef.current.startRowEditMode
passing the ID of the row (only available ifeditMode="row"
).apiRef.current.startRowEditMode({ id: 1 });
Stop editing
When a cell is in edit mode, the user can stop editing with any of the following interactions:
Pressing Escape—this also reverts any changes made
Pressing Tab—this also saves any changes made
Pressing Enter—this also saves any changes made and moves the focus to the next cell in the same column
Clicking outside the cell or row—this also saves any changes made
Calling
apiRef.current.stopCellEditMode({ id, field })
passing the row ID and column field of the cell that's been editedapiRef.current.stopCellEditMode({ id: 1, field: 'name' }); // or apiRef.current.stopCellEditMode({ id: 1, field: 'name', ignoreModifications: true, // will also discard the changes made });
Calling
apiRef.current.stopRowEditMode
passing the ID of the row (only possible ifeditMode="row"
).apiRef.current.stopRowEditMode({ id: 1 }); // or apiRef.current.stopRowEditMode({ id: 1, ignoreModifications: true, // will also discard the changes made });
Disable editing of specific cells within a row
The editable
property controls which cells are editable at the column level.
You can use the isCellEditable
callback prop to define which individual cells the user can edit in a given row.
It is called with a GridCellParams
object and must return true
if the cell is editable, or false
if not.
In the following demo, only the rows with an even Age
value are editable.
The editable cells have a green background for better visibility.
<DataGrid
rows={rows}
columns={columns}
isCellEditable={(params) => params.row.age % 2 === 0}
experimentalFeatures={{ newEditingApi: true }}
/>
Value parser and value setter
You can use the valueParser
property in the column definition to modify the value entered by the user—for example, to convert the value to a different format:
const columns: GridColDef[] = [
{
valueParser: (value: GridCellValue, params: GridCellParams) => {
return value.toLowerCase();
},
},
];
You can use the valueSetter
property of the column definition to customize how the row is updated with a new value.
This lets you insert a value from a nested object.
It is called with an object containing the new cell value to be saved as well as the row that the cell belongs to.
If you are already using a valueGetter
to extract the value from a nested object, then the valueSetter
will probably also be necessary.
const columns: GridColDef[] = [
{
valueSetter: (params: GridValueSetterParams) => {
const [firstName, lastName] = params.value!.toString().split(' ');
return { ...params.row, firstName, lastName };
},
},
];
In the following demo, both the valueParser
and the valueSetter
are defined for the Full name column.
The valueParser
capitalizes the value entered, and the valueSetter
splits the value and saves it correctly into the row model:
Events
The mouse and keyboard interactions that start and stop cell editing do so by triggering the 'cellEditStart'
and 'cellEditStop'
events, respectively.
For row editing, the events are 'rowEditStart'
and 'rowEditStop'
.
You can control how these events are handled to customize editing behavior.
For convenience, you can also listen to these events using their respective props:
onCellEditStart
onCellEditStop
onRowEditStart
onRowEditStop
These events and props are called with an object containing the row ID and column field of the cell that is being edited.
The object also contains a reason
param that specifies which type of interaction caused the event to be fired—for instance, 'cellDoubleClick'
when a double-click initiates edit mode.
The following demo shows how to prevent the user from exiting edit mode when clicking outside of a cell.
To do this, the onCellEditStop
prop is used to check if the reason
is 'cellFocusOut'
.
If that condition is true, it disables the default event behavior.
In this context, the user can only stop editing a cell by pressing Enter, Escape or Tab.
<DataGrid
rows={rows}
columns={columns}
experimentalFeatures={{ newEditingApi: true }}
onCellEditStop={(params: GridCellEditStopParams, event: MuiEvent) => {
if (params.reason === GridCellEditStopReasons.cellFocusOut) {
event.defaultMuiPrevented = true;
}
}}
/>
Controlled mode
Each cell and row has two modes: edit
and view
.
You can control the active mode using the props cellModesModel
and rowModesModel
(only works if editMode="row"
).
The cellModesModel
prop accepts an object containing the mode
(and additional options) for a given column field, in a given row, as in the following example.
The options accepted are the same available in apiRef.current.startCellEditMode
and apiRef.current.stopCellEditMode
.
// Changes the mode of field=name from row with id=1 to "edit"
<DataGrid
cellModesModel={{ 1: { name: { mode: GridCellModes.Edit } } }}
/>
// Changes the mode of field=name from row with id=1 to "view", ignoring modifications made
<DataGrid
cellModesModel={{ 1: { name: { mode: GridCellModes.View, ignoreModifications: true } } }}
/>
For row editing, the rowModesModel
props work in a similar manner.
The options accepted are the same available in apiRef.current.startRowEditMode
and apiRef.current.stopRowEditMode
.
// Changes the mode of the row with id=1 to "edit"
<DataGrid
editMode="row"
rowModesModel={{ 1: { mode: GridRowModes.Edit } }}
/>
// Changes the mode of the row with id=1 to "view", ignoring modifications made
<DataGrid
editMode="row"
rowModesModel={{ 1: { mode: GridRowModes.View, ignoreModifications: true } }}
/>
Additionally, the callback props onCellModesModelChange
and onRowModesModelChange
(only works if editMode="row"
) are available.
Use them to update the respective prop.
In the demo below, cellModesModel
is used to control the mode of selected cell using the external buttons.
For an example using row editing check the full-featured CRUD component.
⚠ The options passed to both model props only take effect when
mode
changes. Updating the params of a cell or row, but keeping the samemode
, makes the cell or row to stay in the same mode. Also, removing one field or row ID from the object will not cause the missing cell or row to go to"view"
mode.
Validation
If the column definition sets a callback for the preProcessEditCellProps
property, then it will be called each time a new value is entered into a cell from this column.
This property lets you pre-process the props that are passed to the edit component.
The preProcessEditCellProps
callback is called with an object containing the following attributes:
id
: the row IDrow
: the row model containing the value(s) of the cell or row before entering edit modeprops
: the props, containing the value after the value parser, that are passed to the edit componenthasChanged
: determines ifprops.value
is different from the last time this callback was called
Data validation is one type of pre-processing that can be done in this way.
To validate the data entered, pass a callback to preProcessEditCellProps
checking if props.value
is valid.
If the new value is invalid, set props.error
to a truthy value and return the modified props, as shown in the example below.
When the user tries to save the updated value, the change will be rejected if the error attribute is truthy (invalid).
const columns: GridColDef[] = [
{
field: 'firstName',
preProcessEditCellProps: (params: GridPreProcessEditCellProps) => {
const hasError = params.props.value.length < 3;
return { ...params.props, error: hasError };
},
},
];
⚠ Changing
props.value
inside the callback has no effect. To pre-process it, use a value parser.
The demo below contains an example of server-side data validation.
In this case, the callback returns a promise that resolves to the modified props.
Note that the value passed to props.error
is passed directly to the edit component as the error
prop.
While the promise is not resolved, the edit component will receive an isProcessingProps
prop with value equal to true
.
⚠ If the user performs an action that saves changes and exits edit mode (e.g. pressing Enter) while the props are still being processed, the changes will be discarded upon exit. To avoid this, it is important to communicate to users when the processing is still occurring. You can use the
isProcessingProps
prop to show a loader while waiting for the server to respond.
Persistence
The processRowUpdate
prop is called when the user performs an action to stop editing.
Use this prop to send the new values to the server and save them into a database or other storage method.
The prop is called with two arguments:
- the updated row with the new values after passing through the
valueSetter
- the values of the row before the cell or row was edited
Once the row is saved, processRowUpdate
must return the row object that will be used to update the internal state.
The value returned is used as an argument to a call to apiRef.current.updateRows
.
If you need to cancel the save process while calling processRowUpdate
—for instance, when a database validation fails, or the user wants to reject the changes—there are two options:
- Reject the promise so that the internal state is not updated and the cell remains in edit mode
- Resolve the promise with the second argument—the original value(s)—so that the internal state is not updated, but the cell exits edit mode
The following demo implements the first option: rejecting the promise.
Instead of validating while typing, it validates in the server.
If the new name is empty, then the promise responsible for saving the row will be rejected and the cell will remain in edit mode.
Additionally, onProcessRowUpdateError
is called to display the error message.
The demo also shows that processRowUpdate
can be used to pre-process the row model that will be saved into the internal state.
To exit edit mode, press Escape or enter a valid name.
Ask for confirmation before saving
The second option—resolving the promise with the second argument—lets the user cancel the save process by rejecting the changes and exiting edit mode.
In this case, processRowUpdate
is resolved with the second argument—the original value(s) of the cell or row.
The following demo shows how this approach can be used to ask for confirmation before sending the data to the server. If the user accepts the change, the internal state is updated with the values. But if the changes are rejected, the internal state remains unchanged, and the cell is reverted back to its original value. The demo also employs validation to prevent entering an empty name.
Create your own edit component
Each of the built-in column types provides a component to edit the value of the cells.
To customize column types, or override the existing components, you can provide a new edit component through the renderEditCell
property in the column definition.
This property works like the renderCell
property, which is rendered while cells are in view mode.
function CustomEditComponent(props: GridRenderEditCellParams) {
return <input type="text" value={params.value} onValueChange={...}>;
}
const columns: GridColDef[] = [
{
field: 'firstName',
renderEditCell: (params: GridRenderEditCellParams) => (
return <CustomEditComponent {...params} />;
),
},
];
The renderEditCell
property receives all params from GridRenderEditCellParams
, which extends GridCellParams
.
Additionally, the props added during pre-processing are also available in the params.
These are the most important params to consider:
value
: contains the current value of the cell in edit mode, overriding the value fromGridCellParams
error
: the error added during validationisProcessingProps
: whetherpreProcessEditCellProps
is being executed or not
Once a new value is entered into the input, it must be sent to the grid.
To do this, pass the row ID, the column field, and the new cell value to a call to apiRef.current.setEditCellValue
.
The new value will be parsed and validated, and the value
prop will reflect the changes in the next render.
function CustomEditComponent(props: GridRenderEditCellParams) {
const { id, value, field } = props;
const apiRef = useGridApiContext();
const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value; // The new value entered by the user
apiRef.current.setEditCellValue({ id, field, value: newValue });
};
return <input type="text" value={value} onValueChange={handleValueChange}>;
}
The following demo implements a custom edit component, based on the Rating
component from @mui/material
, for the Rating column.
With debounce
By default, each call to apiRef.current.setEditCellValue
triggers a new render.
If the edit component requires the user to type a new value, re-rendering the grid too often will drastically reduce performance.
One way to avoid this is to debounce the API calls.
You can use apiRef.current.setEditCellValue
to handle debouncing by setting the debounceMs
param to a positive integer that defines a set time period in milliseconds.
No matter how many times the API method is called, the grid will only be re-rendered after that period of time has passed.
apiRef.current.setEditCellValue({ id, field, value: newValue, debounceMs: 200 });
When the grid is only set to re-render after a given period of time has passed, the value
prop will not be updated on each apiRef.current.setEditCellValue
call.
To avoid a frozen UI, the edit component can keep the current value in an internal state and sync it once value
changes.
Modify the edit component to enable this feature:
function CustomEditComponent(props: GridRenderEditCellParams) {
- const { id, value, field } = props;
+ const { id, value: valueProp, field } = props;
+ const [value, setValue] = React.useState(valueProp);
const apiRef = useGridApiContext();
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value; // The new value entered by the user
- apiRef.current.setEditCellValue({ id, field, value: newValue });
+ apiRef.current.setEditCellValue({ id, field, value: newValue, debounceMs: 200 });
+ setValue(newValue);
};
+ React.useEffect(() => {
+ setValue(valueProp);
+ }, [valueProp]);
+
return <input type="text" value={value} onChange={handleChange}>;
}
With auto-stop
An edit component has "auto-stop" behavior when it stops edit mode as soon as the value is changed.
To picture better, imagine an edit component with a combo, created following the normal steps.
By default, it would require two clicks to change the value of the cell: one click inside the cell to select a new value, and another click outside the cell to save.
This second click can be avoided if the first click also stops the edit mode.
To create an edit component with auto-stop, call apiRef.current.stopCellEditMode
after setting the new value.
Since apiRef.current.setEditCellValue
may do additional processing, you must wait for it to resolve before stopping the edit mode.
Also, it is a good practice to check if apiRef.current.setEditCellValue
has returned true
.
It will be false
if preProcessEditProps
set an error during validation.
const handleChange = async (event: SelectChangeEvent) => {
const isValid = await apiRef.current.setEditCellValue({
id,
field,
value: event.target.value,
});
if (isValid) {
apiRef.current.stopCellEditMode({ id, field });
}
};
The following demo implements an edit component with auto-stop, based on a native Select
component for the Role column.
⚠ We don't recommend using edit components with auto-stop in columns that use long-running
preProcessEditCellProps
because the UI will freeze while waiting forapiRef.current.setEditCellValue
. Instead, use the provided interactions to exit edit mode.
Row editing
Row editing lets the user edit all cells in a row simultaneously.
The same basic rules for cell editing also apply to row editing.
To enable it, change the editMode
prop to "row"
, then follow the same guidelines as those for cell editing to set the editable
property in the definition of the columns that the user can edit.
<DataGrid editMode="row" columns={[{ field: 'name', editable: true }]} />
The following demo illustrates how row editing works. The user can start and stop editing a row using the same actions as those provided for cell editing (e.g. double-clicking a cell).
⚠ By design, when changing the value of a cell all
preProcessEditCellProps
callbacks from other columns are also called. This lets you apply conditional validation where the value of a cell impacts the validation status of another cell in the same row. If you only want to run validation when the value has changed, check if thehasChanged
param istrue
.
Full-featured CRUD component
Row editing makes it possible to create a full-featured CRUD (Create, Read, Update, Delete) component similar to those found in enterprise applications. In the following demo, the typical ways to start and stop editing are all disabled. Instead, use the buttons available in each row or in the toolbar.
Advanced use cases
In the next sections, there examples of how the props provided by editing API can be used to implement complex use cases commonly found in applications.
Conditional validation
When all cells in a row are in edit mode, you can validate fields by comparing their values against one another.
To do this, start by adding the preProcessEditCellProps
as explained in the validation section.
When the callback is called, it will have an additional otherFieldsProps
param containing the props from the other fields in the same row.
Use this param to check if the value from the current column is valid or not.
Return the modified props
containing the error as you would for cell editing.
Once at the least one field has the error
attribute set to a truthy value, the row will not exit edit mode.
The following demo requires a value for the Payment method column only if the Is paid? column is checked:
Linked fields
The options available for one field may depend on the value of another field.
For instance, if the singleSelect
column is used, you can provide a function to valueOptions
returning the relevant options for the value selected in another field, as exemplified below.
const columns: GridColDef[] = [
{
field: 'account',
type: 'singleSelect',
valueOptions: ({ row }) => {
if (!row) {
// The row is not available when filtering this column
return ['Sales', 'Investments', 'Ads', 'Taxes', 'Payroll', 'Utilities'];
}
return row.type === 'Income' // Gets the value of the "type" field
? ['Sales', 'Investments', 'Ads']
: ['Taxes', 'Payroll', 'Utilities'];
},
},
];
The code above is already enough to display different options in the Account column based on the value selected in the Type column.
The only task left is to reset the account once the type is changed.
This is needed because the previously selected account will not exist now in the options.
To solve that, you can create a custom edit component, reusing the built-in one, and pass a function to the onValueChange
prop.
This function should call apiRef.current.setEditCellValue
to reset the value of the other field.
const CustomTypeEditComponent = (props: GridEditSingleSelectCellProps) => {
const apiRef = useGridApiContext();
const handleValueChange = async () => {
await apiRef.current.setEditCellValue({
id: props.id,
field: 'account',
value: '',
});
};
return <GridEditSingleSelectCell onValueChange={handleValueChange} {...props} />;
};
The demo below combines the steps showed above. You can experiment it by changing the value of any cell in the Type column. The Account column is automatically updated with the correct options.
⚠ The call to
apiRef.current.setEditCellValue
returns a promise that must be awaited. For instance, if thesingleSelect
column type is used, not awaiting will cause the other column to be rendered with avalue
that is not in the options.const handleChange = async () => { await apiRef.current.setEditCellValue({ id: props.id, field: 'account', value: '', }); };
A similar behavior can be reproduced with cell editing.
Instead of apiRef.current.setEditCellValue
, the rows
prop must be updated or apiRef.current.updateRows
be used.
Note that the onCellEditStart
and onCellEditStop
props also have to be used to revert the value of the cell changed, in case the user cancels the edit.
getCellMode()
Gets the mode of a cell.
Signature:
getCellMode: (id: GridRowId, field: string) => GridCellMode
getRowMode()
Gets the mode of a row.
Signature:
getRowMode: (id: GridRowId) => GridRowMode
isCellEditable()
Controls if a cell is editable.
Signature:
isCellEditable: (params: GridCellParams) => boolean
setEditCellValue()
Sets the value of the edit cell. Commonly used inside the edit cell component.
Signature:
setEditCellValue: (params: GridEditCellValueParams, event?: MuiBaseEvent) => Promise<boolean> | void
startCellEditMode()
Puts the cell corresponding to the given row id and field into edit mode.
Signature:
startCellEditMode: (params: GridStartCellEditModeParams) => void
startRowEditMode()
Puts the row corresponding to the given id into edit mode.
Signature:
startRowEditMode: (params: GridStartRowEditModeParams) => void
stopCellEditMode()
Puts the cell corresponding to the given row id and field into view mode and updates the original row with the new value stored.
If params.ignoreModifications
is false
it will discard the modifications made.
Signature:
stopCellEditMode: (params: GridStopCellEditModeParams) => void
stopRowEditMode()
Puts the row corresponding to the given id and into view mode and updates the original row with the new values stored.
If params.ignoreModifications
is false
it will discard the modifications made.
Signature:
stopRowEditMode: (params: GridStopRowEditModeParams) => void