mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
3.92k stars 1.19k forks source link

[data grid] `processRowUpdate` does not get fired when grid is styled using `styled` from `@mui/material/styles` #13650

Closed hlavacz closed 4 days ago

hlavacz commented 1 week ago

Steps to reproduce

It is not working even on examples on Docs, when combined. Like:

Minimal reproduction example: https://codesandbox.io/s/zealous-burnell-fm5pnx?file=/src/Demo.tsx

Original code ``` 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 { darken, lighten, styled } from '@mui/material/styles'; import { GridRowsProp, GridRowModesModel, GridRowModes, DataGrid, GridColDef, GridToolbarContainer, GridActionsCellItem, GridEventListener, GridRowId, GridRowModel, GridRowEditStopReasons, GridSlots, } from '@mui/x-data-grid'; import { randomCreatedDate, randomTraderName, randomId, randomArrayItem, } from '@mui/x-data-grid-generator'; const roles = ['Market', 'Finance', 'Development']; const randomRole = () => { return randomArrayItem(roles); }; const initialRows: GridRowsProp = [ { id: randomId(), name: randomTraderName(), age: 25, joinDate: randomCreatedDate(), role: randomRole(), }, { id: randomId(), name: randomTraderName(), age: 36, joinDate: randomCreatedDate(), role: randomRole(), }, { id: randomId(), name: randomTraderName(), age: 19, joinDate: randomCreatedDate(), role: randomRole(), }, { id: randomId(), name: randomTraderName(), age: 28, joinDate: randomCreatedDate(), role: randomRole(), }, { id: randomId(), name: randomTraderName(), age: 23, joinDate: randomCreatedDate(), role: randomRole(), }, ]; 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 = randomId(); setRows((oldRows) => [...oldRows, { id, name: '', age: '', isNew: true }]); setRowModesModel((oldModel) => ({ ...oldModel, [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' }, })); }; return ( ); } export default function FullFeaturedCrudGrid() { const [rows, setRows] = React.useState(initialRows); const [rowModesModel, setRowModesModel] = React.useState({}); 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: 'date', 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 [ } label="Save" sx={{ color: 'primary.main', }} onClick={handleSaveClick(id)} />, } label="Cancel" className="textPrimary" onClick={handleCancelClick(id)} color="inherit" />, ]; } return [ } label="Edit" className="textPrimary" onClick={handleEditClick(id)} color="inherit" />, } label="Delete" onClick={handleDeleteClick(id)} color="inherit" />, ]; }, }, ]; const getBackgroundColor = (color: string, mode: string) => mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); const getHoverBackgroundColor = (color: string, mode: string) => mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); const getSelectedBackgroundColor = (color: string, mode: string) => mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); const getSelectedHoverBackgroundColor = (color: string, mode: string) => mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ '& .rigel-row-blue': { backgroundColor: getBackgroundColor(theme.palette.info.main, theme.palette.mode), '&:hover': { backgroundColor: getHoverBackgroundColor( theme.palette.info.main, theme.palette.mode, ), }, '&.Mui-selected': { backgroundColor: getSelectedBackgroundColor( theme.palette.info.main, theme.palette.mode, ), '&:hover': { backgroundColor: getSelectedHoverBackgroundColor( theme.palette.info.main, theme.palette.mode, ), }, }, }, })); return ( ); } ```

Current behavior

If it is used DataGrid, processRowUpdate is fired, when StyleDataGrid it is not.

Expected behavior

Fired in both cases.

Context

I think it is very obvioius, but if any details needed, let me know.

Your environment

npx @mui/envinfo ``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```

Search keywords: mui datagrid styled processRowUpdate Order ID: 78140

hlavacz commented 5 days ago

I found out, that styled DataGrid fire processRowUpdate if there is not used rowModesModel & onRowModesModelChange. When used normal DataGrid, not styled, it is fired also with using own modes model.

KenanYusuf commented 5 days ago

Hi @hlavacz, thanks for bringing this issue to our attention, I have managed to reproduce it using the code provided.

As a temporary workaround whilst we look into the issue, you could apply styles to the data grid using the sx prop, e.g:

<DataGrid
  rows={rows}
  columns={columns}
  editMode="row"
  rowModesModel={rowModesModel}
  onRowModesModelChange={handleRowModesModelChange}
  onRowEditStop={handleRowEditStop}
  processRowUpdate={processRowUpdate}
  slots={{
    toolbar: EditToolbar as GridSlots['toolbar'],
  }}
  slotProps={{
    toolbar: { setRows, setRowModesModel },
  }}
  sx={(theme) => ({
    '& .rigel-row-blue': {
      backgroundColor: getBackgroundColor(theme.palette.info.main, theme.palette.mode),
      '&:hover': {
        backgroundColor: getHoverBackgroundColor(theme.palette.info.main, theme.palette.mode),
      },
      '&.Mui-selected': {
        backgroundColor: getSelectedBackgroundColor(
          theme.palette.info.main,
          theme.palette.mode,
        ),
        '&:hover': {
          backgroundColor: getSelectedHoverBackgroundColor(
            theme.palette.info.main,
            theme.palette.mode,
          ),
        },
      },
    },
  })}
/>
cherniavskii commented 4 days ago

Hi @hlavacz You should never define a component inside a component:

const Component1 = () => {
  // ❌ new component is created on every rerender
  const Component2 = () => <div>Component 2</div>;

  return <Component2 />
}
// ✅ the component is defined once
const Component2 = () => <div>Component 2</div>;

const Component1 = () => {  
  return <Component2 />
}

Here's a fixed demo based on the code provided: https://codesandbox.io/s/pensive-newton-rwtgph?file=/src/Demo.tsx

github-actions[bot] commented 4 days ago

:warning: This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

@hlavacz: How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.