Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[data grid] Cell update not working after row is copied #15956

Open
pgeday opened this issue Dec 20, 2024 · 2 comments
Open

[data grid] Cell update not working after row is copied #15956

pgeday opened this issue Dec 20, 2024 · 2 comments
Labels
component: data grid This is the name of the generic UI component, not the React module! customization: logic Logic customizability feature: Editing Related to the data grid Editing feature status: waiting for author Issue with insufficient information support: commercial Support request from paid users support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/

Comments

@pgeday
Copy link

pgeday commented Dec 20, 2024

The problem in depth

I have implemented a DataGrid in a NextJS application with JavaScript to enable the importing of large tables from excel and then process them after validation. I am running into the following issue:

  • I can paste multiple rows into my DataGrid and get the updated information for further processing.
  • In my real app I send the copied data through useReducer, so I get it in state, so if the client clicks away and back they still have the data in the table. The table row generation gets the table rows from the state, in case data has already been copied.
  • ISSUE: Once I copied the data, I cannot make changes to individual cells. My updateRow function processes the new information, up to the point of updating the rowsRef.current[index]. The error message is: "Cannot assign to read only property '1' of object '[object Array]'"
  • If I switch screen and back (therefore the table is reloaded), I can edit and update one cell. Then for the next cell update I get the same error.
  • With every screen switch I can edit one cell.

This is my first request here, or on gitHub, I hope the code block works fine with the ``` that gitHub suggested.

Here is my code:

import { useState, useContext, useEffect, useMemo, useCallback, useRef, useReducer } from 'react'
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import { DataGridPremium, GridToolbar, useGridApiRef, gridColumnFieldsSelector, } from '@mui/x-data-grid-premium';
import { darken, lighten, styled } from '@mui/material/styles';


const useColumnsState = (apiRef, columns) => {
  const [widths, setWidths] = useState({});
  const [orderedFields, setOrderedFields] = useState(() =>
    columns.map((column) => column.field),
  );

  const onColumnWidthChange = useCallback(
    ({ colDef, width }) => {
      setWidths((prev) => ({ ...prev, [colDef.field]: width }));
    },
    [setWidths],
  );

  const onColumnOrderChange = useCallback(() => {
    setOrderedFields(gridColumnFieldsSelector(apiRef));
  }, [apiRef, setOrderedFields]);

  const computedColumns = useMemo(
    () =>
      orderedFields.reduce((acc, field) => {
        const column = columns.find((col) => col.field === field);
        if (!column) {
          return acc;
        }
        if (widths[field]) {
          acc.push({
            ...column,
            width: widths[field],
          });
          return acc;
        }
        acc.push(column);
        return acc;
      }, []),
    [columns, widths, orderedFields],
  );

  return { columns: computedColumns, onColumnWidthChange, onColumnOrderChange };
};

const getBackgroundColor = (color, theme, coefficient) => ({
  backgroundColor: darken(color, coefficient),
  ...theme.applyStyles('light', {
    backgroundColor: lighten(color, coefficient),
  }),
});

const StyledDataGrid = styled(DataGridPremium)(({ theme }) => ({
  '& .super-app-theme--Open': {
    ...getBackgroundColor(theme.palette.info.main, theme, 0.7),
    '&:hover': {
      ...getBackgroundColor(theme.palette.info.main, theme, 0.6),
    },
    '&.Mui-selected': {
      ...getBackgroundColor(theme.palette.info.main, theme, 0.5),
      '&:hover': {
        ...getBackgroundColor(theme.palette.info.main, theme, 0.4),
      },
    },
  },
  '& .super-app-theme--Filled': {
    ...getBackgroundColor(theme.palette.success.main, theme, 0.7),
    '&:hover': {
      ...getBackgroundColor(theme.palette.success.main, theme, 0.6),
    },
    '&.Mui-selected': {
      ...getBackgroundColor(theme.palette.success.main, theme, 0.5),
      '&:hover': {
        ...getBackgroundColor(theme.palette.success.main, theme, 0.4),
      },
    },
  },
  '& .super-app-theme--PartiallyFilled': {
    ...getBackgroundColor(theme.palette.warning.main, theme, 0.7),
    '&:hover': {
      ...getBackgroundColor(theme.palette.warning.main, theme, 0.6),
    },
    '&.Mui-selected': {
      ...getBackgroundColor(theme.palette.warning.main, theme, 0.5),
      '&:hover': {
        ...getBackgroundColor(theme.palette.warning.main, theme, 0.4),
      },
    },
  },
  '& .super-app-theme--Rejected': {
    ...getBackgroundColor(theme.palette.error.main, theme, 0.7),
    '&:hover': {
      ...getBackgroundColor(theme.palette.error.main, theme, 0.6),
    },
    '&.Mui-selected': {
      ...getBackgroundColor(theme.palette.error.main, theme, 0.5),
      '&:hover': {
        ...getBackgroundColor(theme.palette.error.main, theme, 0.4),
      },
    },
  },
}));



export default function GridImportGeneralized() {
  const apiRef = useGridApiRef();
  const count = 3;

  const columnStructuresInfo = {
    inputA: { value: '', headerName: 'Input A', description: '', type: 'string', width: 150 },
    inputB: { value: '', headerName: 'Input B', description: '', type: 'string', width: 150 },
    inputC: { value: '', headerName: 'Input C', description: '', type: 'string', width: 150 },
    inputD: { value: '', headerName: 'Input D', description: '', type: 'string', width: 150 },
  }

  const [copied, setCopied] = useState(false);
  const savedData = [
    { inputA: 'Happy', inputB: 'Happy', inputC: 'Happy', inputD: 'Happy' },
    { inputA: 'Joy', inputB: 'Happy', inputC: 'Joy', inputD: 'Happy' },
    { inputA: 'Fun', inputB: 'Fan', inputC: 'Fin', inputD: 'Fon' },
  ]

  const generateColumns = (structure) => {
    const structureItems = Object.keys(structure);
    const columns = structureItems.reduce((acc, curr) => {
      const newAcc = [...acc];
      const specificItem = structure[`${curr}`];
      const { headerName, description, type, width } = specificItem;
      const newItem = { field: `${curr}`, editable: true, headerName, description, type, width, headerClassName: 'super-app-theme--header' }
      newAcc.push(newItem);
      return newAcc;
    }, [])
    return columns;
  }
  const generateRows = (structure, count) => {
    const structureItems = Object.keys(structure);
    const rows = [];
    if (!copied) {
      for (let i = 1; i <= (count); i++) {
        const item = structureItems.reduce((acc, curr, idx) => {
          const newAcc = { ...acc };
          newAcc[`${curr}`] = structure[`${curr}`].value
          return newAcc;
        }, { id: i })
        rows.push(item);
      }
    }
    return rows;
  }

  const [columns, setColumns] = useState(generateColumns(columnStructuresInfo));
  const [rows, setRows] = useState(generateRows(columnStructuresInfo, count));
  let rowsRef = useRef([...rows]);

  const updateRow = useCallback((newRow) => {
    const index = rowsRef.current.findIndex((row) => row.id === newRow.id);
    console.log('New data is coming in fine. : ', { newRow });
    console.log('This next row runs fine with the initial copy, but fails when a call is updated thereafter');
    rowsRef.current[index] = newRow;
    console.log('This runs fine with initial copy, but does not run when a cell is updated thereafter: ', { rows });
    setRows(rowsRef.current);
    console.log('ROW UPDATE Rows: ', { rows });
    setCopied(true);
    return applicableRow;
  }, []);

  const columnsState = useColumnsState(apiRef, columns);

  const onProcessError = error => {
    console.log('Error: ', { error });
    console.log(`'The error message of the rowsRef.current[index] = newRow step: Cannot assign to read only property 'edited row index' of object '[object Array]''`);
  }

  const headerBoxWidth = columns.reduce((acc, curr) => { return acc + curr.width }, 0);

  console.log('GridImport: ', { rows, columns, rowsRef });

  return (
    <Grid container spacing={3} p={3}>
      <Grid item xs={12}>
        <Box
          sx={{
            width: headerBoxWidth,
            '& .super-app-theme--header': {
              backgroundColor: '#3F51B5',
              color: 'white',
            },
          }}
        >
          <StyledDataGrid
            rows={rows} columns={columns} ignoreValueFormatterDuringExport disableRowSelectionOnClick cellSelection
            slots={{ toolbar: GridToolbar, }}
            onColumnWidthChange={columnsState.onColumnWidthChange}
            onColumnOrderChange={columnsState.onColumnOrderChange}
            processRowUpdate={updateRow}
            onProcessRowUpdateError={onProcessError}
            getRowClassName={(params) => `super-app-theme--${params.row.status}`}
          />
        </Box>
      </Grid>
    </Grid>
  );
}

Your environment

System:
OS: Windows 11 10.0.26100
Binaries:
Node: 18.12.1 -
npm: 9.9.3 -
pnpm: Not Found
Browsers:
Chrome: Not Found
Edge: Chromium (131.0.2903.86)
npmPackages:
@emotion/react: ^11.10.5 => 11.10.5
@emotion/styled: ^11.10.5 => 11.10.5
@mui/core-downloads-tracker: 5.16.9
@mui/icons-material: ^5.16.9 => 5.16.9
@mui/material: ^5.16.9 => 5.16.9
@mui/private-theming: 5.16.8
@mui/styled-engine: 5.16.8
@mui/system: 5.16.8
@mui/types: 7.2.19
@mui/utils: 5.16.8
@mui/x-data-grid: 7.23.1
@mui/x-data-grid-premium: ^7.23.1 => 7.23.1
@mui/x-data-grid-pro: 7.23.1
@mui/x-date-pickers: ^5.0.20 => 5.0.20
@mui/x-internals: 7.23.0
@mui/x-license: 7.23.0
@types/react: 18.0.25
react: 18.2.0 => 18.2.0
react-dom: 18.2.0 => 18.2.0
typescript: 5.0.2

Search keywords: DataGrid, cell update, rowsRef not editable

Order ID: 104619

@pgeday pgeday added status: waiting for maintainer These issues haven't been looked at yet by a maintainer support: commercial Support request from paid users labels Dec 20, 2024
@github-actions github-actions bot added component: data grid This is the name of the generic UI component, not the React module! support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/ labels Dec 20, 2024
@michelengelen michelengelen changed the title DataGrid cell update is not working after row copied [data grid] Cell update not working after row is copied Dec 20, 2024
@pgeday
Copy link
Author

pgeday commented Dec 20, 2024

Update on this issue, as I could solve it.

In my real case I always recreate the rows from a dataset saved in state (after every change I send it through the reducer then recreate the rows). I am not sure if it impacts the issue. However, the solution was the following

In the updateRow function I changed

setRows(rowsRef.current);

to

setRows([...rowsRef.current]);

Now after the initial copy of the multiple rows I can update any cells.
I am not sure about why this works (probably something about mutable ref and shallow copy or not with the spread operator), but if someone knows I would still appreciate the insights to learn.

Support, please consider this addressed from my perspective.

@michelengelen
Copy link
Member

All right ... thanks for giving a heads up here @pgeday ... Glad that you could adress it yourself.

Your hunch is only partly correct. You are using useCallback with [] to define the updateRows function and that memoizes the values. That means every time you call it rowsRef.current will be the initial ref.

I would suggest a different approach though to make full use of the internal state management of the grid: instead of using setRows and react state (which will trigger rerenders more often) you should not update it in the updateRow function at all, since the returned row from that function will call the internal updateRows method to update the grid.

So in order to optimize with that in mind you could only update the redux state within your updateRow and then return the mutated row to update the internal state.

const updateRow = useCallback((newRow) => {
  const index = rowsRef.current.findIndex((row) => row.id === newRow.id);
  rowsRef.current[index] = newRow;
  // update the redux state in the next step
  ...
  setCopied(true);
  return newRow;
}, []);

Questions about this?

@michelengelen michelengelen added status: waiting for author Issue with insufficient information feature: Editing Related to the data grid Editing feature customization: logic Logic customizability and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Dec 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: data grid This is the name of the generic UI component, not the React module! customization: logic Logic customizability feature: Editing Related to the data grid Editing feature status: waiting for author Issue with insufficient information support: commercial Support request from paid users support: premium standard Support request from a Premium standard plan user. https://mui.com/legal/technical-support-sla/
Projects
None yet
Development

No branches or pull requests

2 participants