Search code examples
javascriptreactjsangularmaterial-uidatagrid

Prevent triggering auto save in material ui datagrid


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>
  );
}

Solution

  • 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>
      );
    }
    

    stackblitz