Search code examples
reactjsmaterial-uimui-x

How to pass state between renderCell components in Material UI Data Grid


How can I change the MenuItems of one Select when another Select component changes using DataGrid? I need to be able to pass the state of one Select component to the other, but I'm not sure how when using renderCell.

For example, let's say I have the following object:

const data = {
  "/path/to/file1.csv": {
    parameters: ["Parameter 1", "Parameter 2", "Parameter 3"],
  },
  "/path/to/file2.csv": {
    parameters: ["Parameter 2", "Parameter 3", "Parameter 4"],
  },
  "/path/to/file3.csv": {
    parameters: ["Parameter 5", "Parameter 6", "Parameter 7"],
  },
};

In my DataGrid table, every time I add a new row with the click of a button, the first cell has a Select component containing Object.keys(data).

The second cell contains another Select component. I want this Select component to contain parameters that are dependent on the value selected. For example, if /path/to/file1.csv is selected, I want to make available those parameters (Parameter 1, Parameter 2, Parameter 3), but if /path/to/file3.csv is selected, I want to make available those parameters (Parameter 5, Parameter 6, Parameter 7).

Here's my component:

import * as React from "react";
import PropTypes from "prop-types";
import { Button, Select, MenuItem } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { DataGrid, GridActionsCellItem } from "@mui/x-data-grid";

const FileSelect = (props) => {
  const { value } = props;
  const [file, setFile] = React.useState("");

  const handleChange = (event) => {
    setFile(event.target.value);
  };

  return (
    <Select id="file-select" value={file} onChange={handleChange} fullWidth>
      {value?.map((item, index) => (
        <MenuItem key={index} value={item}>
          {item}
        </MenuItem>
      ))}
    </Select>
  );
};

FileSelect.propTypes = {
  value: PropTypes.array,
};

const ParameterSelect = (props) => {
  const { value } = props;
  const [parameter, setParameter] = React.useState("");

  const handleChange = (event) => {
    setParameter(event.target.value);
  };

  return (
    <Select
      id="parameter-select"
      value={parameter}
      onChange={handleChange}
      fullWidth
    >
      {value?.map((item, index) => (
        <MenuItem key={index} value={item}>
          {item}
        </MenuItem>
      ))}
    </Select>
  );
};

export default function DataGridTable(props) {
  const { data } = props;
  const files = Object.keys(data);

  const [rows, setRows] = React.useState([]);

  const columns = [
    {
      field: "file",
      headerName: "File",
      // width: 200,
      flex: 1,
      renderCell: FileSelect,
    },
    {
      field: "x",
      headerName: "X",
      // width: 200,
      flex: 0.5,
      renderCell: ParameterSelect,
    },
    {
      field: "actions",
      headerName: "Delete",
      type: "actions",
      width: 80,
      getActions: (params) => [
        <GridActionsCellItem
          icon={<DeleteIcon />}
          label="Delete"
          onClick={deleteRow(params.id)}
        />,
      ],
    },
  ];

  const handleClick = () => {
    const newRow = {
      id: rows.length + 1,
      file: files,
      x: [],
    };
    setRows((prevState) => [...prevState, newRow]);
  };

  const deleteRow = React.useCallback(
    (id) => () => {
      setTimeout(() => {
        setRows((prevRows) => prevRows.filter((row) => row.id !== id));
      });
    },
    []
  );

  return (
    <div>
      <Button variant="contained" onClick={handleClick}>
        Add row
      </Button>
      <div style={{ height: 300, width: "100%" }}>
        <DataGrid rows={rows} columns={columns} disableSelectionOnClick />
      </div>
    </div>
  );
}

Solution

  • The simplest way that I could think to accomplish this is by adding an extra field to the column definition as an "easy" place to store the selected value.

    ...
    
    const FileSelect = (props) => {
      const { value, row } = props;
    
      const [file, setFile] = React.useState("");
    
      const handleChange = (event) => {
        setFile(event.target.value);
        // Set the value here
        row.selectedFile = event.target.value;
      };
    
      return (
        <Select id="file-select" value={file} onChange={handleChange} fullWidth>
          {value?.map((item, index) => (
            <MenuItem key={index} value={item}>
              {item}
            </MenuItem>
          ))}
        </Select>
      );
    };
    
    ...
    
    {
       field: "selectedFile",
       hideable: true
    },
    
    ...
    

    Then set the selected value (file) in the FileSelect parent value in the selectedFile column. Then all that was left to do was to make the parameters lookup values available to the ParameterSelect. Again, I just stuffed them into the renderCell props, but this could be done better as well:

    ...
    
    {
       field: "x",
       headerName: "X",
       flex: 0.5,
       // Passing the entire original data in as an extra param, for demonstration purposes
       renderCell: (props) => ParameterSelect({ ...props, data })
    },
    
    ...
    

    Finally, just hide the selectedFile column:

    ...
    
    <DataGrid
      rows={rows}
      columns={columns}
      disableSelectionOnClick
      // Hiding the extra field
      columnVisibilityModel={{
         selectedFile: false
      }}
    />
    
    ...
    

    Producing this: (I changed your values to make them easier to read while I was working)

    I think this is what you wanted

    Working CodeSandBox: https://codesandbox.io/s/prod-sun-bdvcu0?file=/demo.js:842-854