A. Aziz
A. Aziz

Reputation: 57

How can I dynamically generate several MUI Select elements and their options?

From existing data, I am generating a number of MUI "<Select>" elements dynamically. However when interacted with one of the "<Select>" elements, the selected value is applied to all "<Select>" elements.

For example, this is given data:

const groups = [{name: "group1",group_id: 1}, {name: "group2", group_id: 2}];

const options = [
  {name: "option1", option_id: 1, group_id: 1},
  {name: "option2", option_id: 2, group_id: 1},
  {name: "option3", option_id: 3, group_id: 1},
  {name: "option4", option_id: 4, group_id: 2},
  {name: "option5", option_id: 5, group_id: 2},
  {name: "option6", option_id: 6, group_id: 2},
]

Using this data, two "<Select>" elements are generated. Each has different 3 options. When I select using first "<Select>" element, value in second "<Select>" element also changes.

My code for generating elements:

const [selectedElements, setSelectedElements] = React.useState([]);
    
const onSelectionChange = (event, serviceGroup) => {
  const value = event.target.value;
    
  setSelectedElements(
    typeof value === 'string' ? value.split(',') : value,
  ); 
}

return (
<div>
  {
    groups.map(function(group) {
        return (
            <Select
                multiple
                value={selectedElements}
                onChange={(e)=>onSelectionChange(e, group)}
                id={group.group_id}
                renderValue={(selected) => selected.join(', ')} 
            >
                {
                    options.map(function (option) {
                        if (option.group_id === group.group_id) {
                            return (
                                <MenuItem
                                    key={option.option_id}
                                    value={option.name}
                                >
                                    <ListItemText primary={option.name} />
                                    <Checkbox checked={selectedElements.indexOf(option.name) > -1}/>
                                </MenuItem>
                            )
                        }
                    })
                }
            </Select>
        )
    })
  }
</div>
)

I know that "value={selectedElements}" will cause every dynamically generated "<Select>" element have the same value, thus if one of them changes its value, others will change their value too.

To fix this, I tried using const [selectedElementsObj, setSelectedElementsObj] = React.useState({}); - an object state, and then replace "value={selectedElements}" with "value={selectedElementsObj[group.name]}". While inside the onSelectionChange function, I created an object of arrays, so that inside selectedElementsObj I will have separate array for each "<Select" element:

const objCopy = Object.assign({}, selectedElementsObj);
objCopy[group.name] = typeof value === 'string' ? value.split(',') : value;
setSelectedElementsObj(objCopy);

The problem with my attempt is that it just not renders and I get white screen. I hope I described my problem clearly, and here is codesandbox with the example i have shown here !

Upvotes: 1

Views: 2309

Answers (1)

KvS
KvS

Reputation: 82

The problem here is that onChange event, you were not setting value for that particular item (as you also said). So to identify each element differently there has to have certain key in selectedElements or selectedElementsObj based on which we can know this value is to be set for that certain id.

To solve this what I did was setting seletedElements with groups array of obj.

const [selectedElements, setSelectedElements] = React.useState(groups);

And then used this to map over Select component, and changed its value based selectedElements value.

selectedElements.map(function (group) {
      return (
        <Select
          style={{ marginBottom: "10px" }}
          multiple
          value={group?.value || []}
          onChange={(e) => onSelectionChange(e, group)}
          id={group.group_id}
          renderValue={(selected) => selected.join(", ")}
          sx={{
            "& legend": { display: "none" },
            "& fieldset": { top: 0 }
          }}
        >
          {options.map(function (option) {
            if (option.group_id === group.group_id) {
              return (
                <MenuItem key={option.option_id} value={option.name}>
                  <ListItemText primary={option.name} />
                  <Checkbox
                    checked={selectedElements.indexOf(option.name) > -1}
                  />
                </MenuItem>
              );
            }
          })}
        </Select>
      );
    })}

And the last part left was assigning (rather creating here) that value in that obj, for which I did this inside onChange function call:

selectedElements.map(item => {
  if(item.group_id === group.group_id)
  {
    item.value = typeof value === "string" ? value.split(",") : value
  }
})

Upvotes: 2

Related Questions