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/
4.53k stars 1.32k forks source link

How to change value of different cell based on dropdown option selected in another cell in Mui datagrid? #8121

Open yashwant-raut opened 1 year ago

yashwant-raut commented 1 year ago

I have created Mui [datagrid] table with tree data. I have one column in which I have rendered html select tag with option fetched from GridRowsProp using custom renderCell function..

What I want. I have two elements in rows data experience and salary as array. I am using experience array data to feed as option to select tag which will be rendered in each cell of Experience column.

image

So let's say I choose "One" from the select dropdown. It appears at index 0. I want to take the same index value out of the salary array and put it in the appropriate column cell. i.e in same row

here is my rows data sample

  {
    hierarchy: ["Sarah"],
    jobTitle: "Head of Human Resources",
    recruitmentDate: new Date(2020, 8, 12),
    experience: ["One", "Two", "Three", "Four"],
    salary: [12, 32, 42, 54],
    id: 0
  },
  {
    hierarchy: ["Thomas"],
    jobTitle: "Head of Sales",
    recruitmentDate: new Date(2017, 3, 4),
    experience: ["One", "Two", "Three", "Four"],
    salary: [12, 32, 42, 54],
    id: 1
  },
  {
    hierarchy: ["Mary", "Linda", "William"],
    jobTitle: "Back-end developer",
    recruitmentDate: new Date(2018, 4, 19),
    experience: ["One", "Two", "Three", "Four"],
    salary: [12, 32, 42, 54],
    id: 14
  }
];```

here is how am creating column

``` const columns: GridColumns = [
  { field: "jobTitle", headerName: "Job Title", width: 200 },
  {
    field: "recruitmentDate",
    headerName: "Recruitment Date",
    type: "date",
    width: 150
  },
  {
    field: "experience",
    headerName: "Experience",
    renderCell: (params) => {
      return <Dropdown props={params} />;
    }
  },

  {
    field: "salary",
    headerName: "Salary",
    valueGetter: (params) => {
      const salaryArr = params.row.salary as number[];
      return salaryArr[0];
    }
  }
];

I have created a basic application with tree data. Here is link to Codesandbox have added all things Which I mentioned above. It will be great if you check out this code sandbox and give me any pointers about how should I proceed.

yashwant-raut commented 1 year ago

I am conducting a feasibility study because I intend to buy a pro/premium plan if Mui datagrid can support all of our requirements. Any assistance or advice from the maintainers would be greatly appreciated.

m4theushw commented 1 year ago

We have a example similar to what you're looking for in https://mui.com/x/react-data-grid/recipes-editing/#linked-fields. You also need to use editMode="row" to have all cells in edit mode.

yashwant-raut commented 1 year ago

I appreciate your response, @m4theushw. I followed the link you supplied, but it did not help me with my issue. The example provided in the link above compares previously saved value. pardon me for not being clear in the question the experience value array in my case will be dynamic. For the time being, I've kept everything static to keep things simple.
Dropdown will be dynamic i.e. options value will not be same so I cannot follow same methodology in given in example that compare the value selected and then produce results for that cell.

Here is what I have Tried

function handleChange(event) {
    console.log(event.target.selectedIndex);
    setSalaryValue(event.target.selectedIndex);
  }

created handleChange function which will get fired ever time user select option from dropdown. and from selected option I will get index and then set react state which I have given to salary column

 {
      field: "salary",
      headerName: "Salary",
      valueGetter: (params) => {
        const salaryArr = params.row.salary as number[];
        return salaryArr[salaryValue];
      }
    }

when I select dropdown option it sets salary value correctly for that row but The problem I am facing with this is it also change salary value of all other rows which I do not want. So am I stuck here

here is link to codesandbox which have above example Please have a look

Thanks again for the reply.

yaredtsy commented 1 year ago

hey @yashwant-raut
you need to store every rows Id and their selected value. so you can filter the right value for every row on valueGetter.

const [salaryValue, setSalaryValue] = useState([{rowId:0,value:0});
....
{
      field: "salary",
      headerName: "Salary",
      valueGetter: (params) => {
        const salaryArr = params.row.salary as number[];
        const {id} = params
        return salaryArr[salaryValue.find(value=>value.rowId == id).value];
      }
    }

or you can get the selectedIndex from the HTML element.

    {
      field: "salary",
      headerName: "Salary",
      valueGetter: (params) => {
        const salaryArr = params.row.salary as number[];
        const select = apiRef.current.getCellElement(params.id, "experience");
        if (select) {
          const index = (select.children[0] as HTMLSelectElement).selectedIndex;
          if (select) return salaryArr[index];
        }
      }
    }

Demo: https://codesandbox.io/s/festive-dijkstra-pk0z6g?file=/demo.tsx:4368-4767

yashwant-raut commented 1 year ago

@yaredtsy Thank you for taking the time to read my query, explore the sandbox, and comprehend my issue. The response you provided really helped me comprehend how various APIs might be used to attain my goal. Thanks again.

yashwant-raut commented 1 year ago

hey @yashwant-raut you need to store every rows Id and their selected value. so you can filter the right value for every row on valueGetter.

const [salaryValue, setSalaryValue] = useState([{rowId:0,value:0});
....
{
      field: "salary",
      headerName: "Salary",
      valueGetter: (params) => {
        const salaryArr = params.row.salary as number[];
        const {id} = params
        return salaryArr[salaryValue.find(value=>value.rowId == id).value];
      }
    }

or you can get the selectedIndex from the HTML element.

    {
      field: "salary",
      headerName: "Salary",
      valueGetter: (params) => {
        const salaryArr = params.row.salary as number[];
        const select = apiRef.current.getCellElement(params.id, "experience");
        if (select) {
          const index = (select.children[0] as HTMLSelectElement).selectedIndex;
          if (select) return salaryArr[index];
        }
      }
    }

Demo: https://codesandbox.io/s/festive-dijkstra-pk0z6g?file=/demo.tsx:4368-4767

Hi @yaredtsy Can we do this using Mui Select instead of native select. I want to give some styling to dropdown and turns out we cannot add styling to native select. that's why I switch to Mui select but this code was not working. I find out that Mui select does not use select internally so I cannot retrieve index Using this code

  const select = apiRef.current.getCellElement(params.id, "experience");
        if (select) {
           const index = (select.children[0] as HTMLSelectElement).selectedIndex;
           if (select) return salaryArr[index];
         }

How should I Proceed. Which API will give me selected Index here.

yaredtsy commented 1 year ago

it would be easier for you if you used singleSelect column type with edit API , Demo : https://codesandbox.io/s/nostalgic-herschel-cwv1nd?file=/demo.tsx

yashwant-raut commented 1 year ago

it would be easier for you if you used singleSelect column type with edit API , Demo : https://codesandbox.io/s/nostalgic-herschel-cwv1nd?file=/demo.tsx

Hello @yaredtsy, I'm really extremely thankful for your prompt response and this seems to be exactly what I wanted. Just one question as I observed the saving values are not updated Immediately as I select option from dropdown they gets updated once I click outside that cell is this behavior normal or I can look into some other API. Thanks again for this Going through trouble of setting Up codesandbox

yaredtsy commented 1 year ago

override the renderEditCell with this

const CustomEditCell = (params: GridEditSingleSelectCellProps) => {
  const ref = useGridApiContext();
  return (
    <GridEditSingleSelectCell
      {...params}
      onValueChange={async (event, newValue) => {
        await ref.current.setEditCellValue(
          { id: params.id, field: params.field, value: newValue },
          event
        );
        ref.current.stopCellEditMode({ id: params.id, field: params.field });
      }}
    />
  );
};

Demo: https://codesandbox.io/s/nostalgic-herschel-cwv1nd?file=/demo.tsx

you can check the documentation about the edit APIs at https://mui.com/x/react-data-grid/editing/ and also check about column definitions it helps you understand how you can customize cells https://mui.com/x/react-data-grid/column-definition/#rendering-cells

yashwant-raut commented 1 year ago

override the renderEditCell with this

const CustomEditCell = (params: GridEditSingleSelectCellProps) => {
  const ref = useGridApiContext();
  return (
    <GridEditSingleSelectCell
      {...params}
      onValueChange={async (event, newValue) => {
        await ref.current.setEditCellValue(
          { id: params.id, field: params.field, value: newValue },
          event
        );
        ref.current.stopCellEditMode({ id: params.id, field: params.field });
      }}
    />
  );
};

Demo: https://codesandbox.io/s/nostalgic-herschel-cwv1nd?file=/demo.tsx

you can check the documentation about the edit APIs at https://mui.com/x/react-data-grid/editing/ and also check about column definitions it helps you understand how you can customize cells https://mui.com/x/react-data-grid/column-definition/#rendering-cells Wow, this was quick; I apologise for not responding yesterday. I greatly appreciate the time you are taking to answer my questions. Your response is accurate . It is resolving the problem I described earlier.

yashwant-raut commented 1 year ago

override the renderEditCell with this

const CustomEditCell = (params: GridEditSingleSelectCellProps) => {
  const ref = useGridApiContext();
  return (
    <GridEditSingleSelectCell
      {...params}
      onValueChange={async (event, newValue) => {
        await ref.current.setEditCellValue(
          { id: params.id, field: params.field, value: newValue },
          event
        );
        ref.current.stopCellEditMode({ id: params.id, field: params.field });
      }}
    />
  );
};

Demo: https://codesandbox.io/s/nostalgic-herschel-cwv1nd?file=/demo.tsx

you can check the documentation about the edit APIs at https://mui.com/x/react-data-grid/editing/ and also check about column definitions it helps you understand how you can customize cells https://mui.com/x/react-data-grid/column-definition/#rendering-cells

I want to customize singleSelect Dropdown options. I want to add tooltips to dropdown options so I have tried to add this using this way where I have provided Tooltip in the row itself. But here I need to do this for each and every row and in that for each every valueOptions object element which is very hard coded approach to solve this issue.

  experienceOption: [
        {
          value: "The quick brown fox jumps over the lazy dog",
          label: (
            <Tooltip title="The quick brown fox jumps over the lazy dog">
              {
                <span
                  style={{
                    width: "132px",
                    textOverflow: "ellipsis",
                    overflow: "hidden",
                    whiteSpace: "nowrap"
                  }}
                >
                  The quick brown fox jumps over the lazy dog
                </span>
              }
            </Tooltip>
          )
        },
        {
          value: "Two",
          label: <Tooltip title="Two">{<span>Two</span>}</Tooltip>
        },
        {
          value: "Three",
          label: <Tooltip title="Three">{<span>Three</span>}</Tooltip>
        },
        {
          value: "Four",
          label: <Tooltip title="Four">{<span>Four</span>}</Tooltip>
        }
      ],

Is there any other way can we do this like in CustomEditCell function

const CustomEditCell = (params: GridEditSingleSelectCellProps) => {
  const ref = useGridApiContext();
  return (
    <GridEditSingleSelectCell
      {...params}
      onValueChange={async (event, newValue) => {
        await ref.current.setEditCellValue(
          { id: params.id, field: params.field, value: newValue },
          event
        );
        ref.current.stopCellEditMode({ id: params.id, field: params.field });
      }}
    />
  );
};

It would be wonderful if you could offer some insight on this. here is codesandbox link where I reproduced this

yaredtsy commented 1 year ago

you can achieve this by overriding the baseSelectOption. check this article for more info about overriding components https://mui.com/x/react-data-grid/components/.

Demo: https://codesandbox.io/s/objective-morning-3bk3ro?file=/demo.tsx

yashwant-raut commented 1 year ago

you can achieve this by overriding the baseSelectOption. check this article for more info about overriding components https://mui.com/x/react-data-grid/components/.

Demo: https://codesandbox.io/s/objective-morning-3bk3ro?file=/demo.tsx

This is exactly what I am looking for. thanks for the documentation I got the idea about how to override components and as always big thumbs up for providing Code sandbox it really helps in understanding.

yashwant-raut commented 1 year ago

you can achieve this by overriding the baseSelectOption. check this article for more info about overriding components https://mui.com/x/react-data-grid/components/.

Demo: https://codesandbox.io/s/objective-morning-3bk3ro?file=/demo.tsx

Hey If you notice in this application you can check when we change dropdown value of first row. Entire row gets shifted to the bottom of the table I find it this issue in this codesandbox only. Tried to debug it but seems there is no error produced in console log. Unable to wrap my head around it RowShift

Do you know why it is happening is it related to filtering?

yaredtsy commented 1 year ago

this issue has been reported at #8224.

yashwant-raut commented 1 year ago

this issue has been reported at #8224.

okay thanks for the update

yashwant-raut commented 1 year ago

Hello @yaredtsy hope you are doing well. I have new issue which is very strange. selected Input values of singleSelect looses its value after we set state in application.

I have tried to create demo where I am changing value of state variable on single select edit i.e preProcessEditCellProps function. I am able to get the value and set to state but whenever I change the option from any single select all the other single selects return to their default values. So, whatever the user selected, those data are lost.

And this behavior is not normal Selected input should remain intact. I am attaching gif here you can see whenever I am selecting option from second dropdown. First Dropdown loose its selected value

ResetSelectNew

Can you please look into it. It would be wonderful if you could offer some insight on this.

yaredtsy commented 1 year ago

hey, could you also share the codesandbox, it will be easier to debug if I can see the code.

yashwant-raut commented 1 year ago

hey, could you also share the codesandbox, it will be easier to debug if I can see the code.

Hey apologies for not providing codesandbox link totally forgot here is the Link

yashwant-raut commented 1 year ago

hey, could you also share the codesandbox, it will be easier to debug if I can see the code.

Hey Have you check the codesandbox is there any issue in it?

yaredtsy commented 1 year ago

i have checked it because the rows variable is declared in the component and when you update the state the rows variable is re-created which will re-initialize the datagrid. use useMemo or move the variable out of the component.