I created a data-grid
and tried to add some records. Add record will create a new empty row. Without filling, I tried to press TAB key, but when focusing on save button, it automatically saved. How can I prevent this behaviour? I need to manually press enter while focusing on save button.
Code below:
"use client"
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import {
GridRowsProp,
GridRowModesModel,
GridRowModes,
DataGrid,
GridColDef,
GridToolbarContainer,
GridActionsCellItem,
GridEventListener,
GridRowId,
GridRowModel,
GridRowEditStopReasons,
} from '@mui/x-data-grid';
const roles = ['Market', 'Finance', 'Development'];
const initialRows: GridRowsProp = [
{
id: 1,
name: "Ash",
age: 25,
joinDate: "10-10-2023",
role: "Market",
},
];
interface EditToolbarProps {
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel,
) => void;
}
function EditToolbar(props: EditToolbarProps) {
const { setRows, setRowModesModel } = props;
const handleClick = () => {
const id = 10;
setRows((oldRows) => [...oldRows, { id, name: '', age: '', isNew: true }]);
setRowModesModel((oldModel) => ({
...oldModel,
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
}));
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Add record
</Button>
</GridToolbarContainer>
);
}
export default function FullFeaturedCrudGrid() {
const [rows, setRows] = React.useState(initialRows);
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>({});
const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
if (params.reason === GridRowEditStopReasons.rowFocusOut) {
event.defaultMuiPrevented = true;
}
};
const handleEditClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};
const handleSaveClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};
const handleDeleteClick = (id: GridRowId) => () => {
setRows(rows.filter((row) => row.id !== id));
};
const handleCancelClick = (id: GridRowId) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true },
});
const editedRow = rows.find((row) => row.id === id);
if (editedRow!.isNew) {
setRows(rows.filter((row) => row.id !== id));
}
};
const processRowUpdate = (newRow: GridRowModel) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
return updatedRow;
};
const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
setRowModesModel(newRowModesModel);
};
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', width: 180, editable: true },
{
field: 'age',
headerName: 'Age',
type: 'number',
width: 80,
align: 'left',
headerAlign: 'left',
editable: true,
},
{
field: 'joinDate',
headerName: 'Join date',
type: 'string',
width: 180,
editable: true,
},
{
field: 'role',
headerName: 'Department',
width: 220,
editable: true,
type: 'singleSelect',
valueOptions: ['Market', 'Finance', 'Development'],
},
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
sx={{
color: 'primary.main',
}}
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>,
];
}
return [
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
];
return (
<Box
sx={{
height: 500,
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<DataGrid
rows={rows}
columns={columns}
editMode="row"
rowModesModel={rowModesModel}
onRowModesModelChange={handleRowModesModelChange}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
slots={{
toolbar: EditToolbar,
}}
slotProps={{
toolbar: { setRows, setRowModesModel },
}}
/>
</Box>
);
}
I checked your code, there only seems to be a problem with multiple added rows having the same ID 10
, after I used state
variable count
to manage the id field, the output looked fine, hope it fixes your issue, please share back the stackblitz with the issue replicated if my answer does not help!
...
function EditToolbar(props: EditToolbarProps) {
const [count, setCount] = React.useState(10); // <- changed here
const { setRows, setRowModesModel } = props;
const handleClick = () => {
setCount(() => count + 1); // <- changed here
setRows((oldRows) => [
...oldRows,
{ id: count, name: '', age: '', isNew: true },
]);
setRowModesModel((oldModel) => ({
...oldModel,
[count]: { mode: GridRowModes.Edit, fieldToFocus: 'name' }, // <- changed here
}));
};
...
full code
'use client';
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import {
GridRowsProp,
GridRowModesModel,
GridRowModes,
DataGrid,
GridColDef,
GridToolbarContainer,
GridActionsCellItem,
GridEventListener,
GridRowId,
GridRowModel,
GridRowEditStopReasons,
} from '@mui/x-data-grid';
const roles = ['Market', 'Finance', 'Development'];
const initialRows: GridRowsProp = [
{
id: 1,
name: 'Ash',
age: 25,
joinDate: '10-10-2023',
role: 'Market',
},
];
interface EditToolbarProps {
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (
newModel: (oldModel: GridRowModesModel) => GridRowModesModel
) => void;
}
function EditToolbar(props: EditToolbarProps) {
const [count, setCount] = React.useState(10);
const { setRows, setRowModesModel } = props;
const handleClick = () => {
setCount(() => count + 1);
setRows((oldRows) => [
...oldRows,
{ id: count, name: '', age: '', isNew: true },
]);
setRowModesModel((oldModel) => ({
...oldModel,
[count]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
}));
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Add record
</Button>
</GridToolbarContainer>
);
}
export default function BasicTable() {
const [rows, setRows] = React.useState(initialRows);
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>(
{}
);
const handleRowEditStop: GridEventListener<'rowEditStop'> = (
params,
event
) => {
if (params.reason === GridRowEditStopReasons.rowFocusOut) {
event.defaultMuiPrevented = true;
}
};
const handleEditClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};
const handleSaveClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};
const handleDeleteClick = (id: GridRowId) => () => {
setRows(rows.filter((row) => row.id !== id));
};
const handleCancelClick = (id: GridRowId) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true },
});
const editedRow = rows.find((row) => row.id === id);
if (editedRow!.isNew) {
setRows(rows.filter((row) => row.id !== id));
}
};
const processRowUpdate = (newRow: GridRowModel) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
return updatedRow;
};
const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
setRowModesModel(newRowModesModel);
};
const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', width: 180, editable: true },
{
field: 'age',
headerName: 'Age',
type: 'number',
width: 80,
align: 'left',
headerAlign: 'left',
editable: true,
},
{
field: 'joinDate',
headerName: 'Join date',
type: 'string',
width: 180,
editable: true,
},
{
field: 'role',
headerName: 'Department',
width: 220,
editable: true,
type: 'singleSelect',
valueOptions: ['Market', 'Finance', 'Development'],
},
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
sx={{
color: 'primary.main',
}}
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon tabIndex={0} />}
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>,
];
}
return [
<GridActionsCellItem
tabIndex={0}
icon={<EditIcon />}
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem
tabIndex={0}
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
];
return (
<Box
sx={{
height: 500,
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<DataGrid
rows={rows}
columns={columns}
editMode="row"
rowModesModel={rowModesModel}
onRowModesModelChange={handleRowModesModelChange}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
slots={{
toolbar: EditToolbar,
}}
slotProps={{
toolbar: { setRows, setRowModesModel },
}}
/>
</Box>
);
}