Material UI select multiple component using an object in the value property, adds duplicate entry
Asked Answered
S

2

5

I have a multi select that i pass an object into the value property with the structure:

enter image description here

The select component looks like this:

<Select
            multiple
            value={entities}
            onChange={handleChange}
            input={<Input />}
            renderValue={(selected) => (
              <div className={classes.chips}>
                {selected.map((value) => (
                  <Chip
                    key={value.entityId}
                    label={value.entityName}
                    className={classes.chip}
                  />
                ))}
              </div>
            )}
          >
            {props.entityList.map((item, index) => (
              <MenuItem key={item.entityId} value={item}>
                {item.entityName}
              </MenuItem>
            ))}
          </Select>

when the select pops up it shows the entity name correct but does not show it as selected in the dropdown.

If i select this item in the drop down it adds another entry which has the same id and same name rather than removing the item that is already there, this newly added duplicate can be removed and is highlighted when selected so the functionality works somewhat.

I am storing entities in the state like so that comes in from a parent component:

enter image description here

Solution:

I have made sure that the same object is being used across the select, the initial object was not the same as the objects that where being assigned in the on Change function. this fixed my problem.

const [entities, setEntity] = React.useState(
props.entityList.filter((e) =>
  props.assignedEntities.some((ae) => e.entityId === ae.entityId)
)

);

Subject answered 15/6, 2020 at 14:4 Comment(4)
When using objects as the values for a Select, they will only be considered matching if they are the exact same object. Two different objects that happen to have the same content will not be considered matching.Bo
@RyanCogswell This makes sense, so are you saying here my props.assignedEntities is not match even.target.value. Can you recommend a solution.Subject
Without seeing a full reproduction of your problem (particularly how props.entityList and props.assignedEntities are managed), it is difficult to recommend a solution. My suspicion is that props.assignedEntities has separate copies of the objects in props.entityList rather than reusing the exact same objects. I would recommend initializing your entities state using props.entityList.filter(...) where you filter by the assigned entity ids such that entities is guaranteed to reuse objects from props.entityList.Bo
@RyanCogswell Awesome i understand this concept now and will defo help me going forward, i have updated my answer to include the fix, Thanks.Subject
C
6

I had exact problem and I solved it in the following way.

In my example I'm using role to user assignment:

Here is my role object:

Role
-----
id,
name,
access,
createdUtc

My component receives these props:

allRoles
user // that has user.roles

Here I extract only ids of the roles:

 const [selectedRoleIds, setSelectedRoleIds] = useState([]);

 useEffect(() => {
    if(user) {
      setSelectedRoleIds(user.roles.map(i => i.id));
    }

 }, [user]);

My multi select component:

<Select
    multiple
    value={selectedRoleIds}
    onChange={handleRoleChange}
    input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
    renderValue={(selected) => (
    <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
        {selected.map((roleId) => (
        <Chip key={roleId} label={allRoles?.find(e => e.id === roleId).name} />
        ))}
    </Box>
    )}
    MenuProps={MenuProps}
>
    {roles.map((role) => (
    <MenuItem key={role.id} value={role.id}>
        <Checkbox checked={selectedRoleIds.includes(role.id)} />
        <ListItemText primary={role.name} />
    </MenuItem>
    ))}                      
</Select>

Handling role change is easy as:

  const handleRoleChange = (event) => {
    const {value} = event.target;
    setSelectedRoleIds(value);

  };

enter image description here

Camarata answered 17/2, 2022 at 17:56 Comment(0)
E
0

My version for this:

    import React, { useState } from 'react';
import { Select, MenuItem, InputLabel, FormControl } from '@material-ui/core';

interface Option {
  id: number;
  name: string;
}

const options: Option[] = [
  { id: 1, name: 'Option 1' },
  { id: 2, name: 'Option 2' },
  { id: 3, name: 'Option 3' },
];

const MultipleSelect: React.FC = () => {
  const [selectedOptions, setSelectedOptions] = useState<Option[]>([]);

  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
    const values = event.target.value as number[];
    const selected = values.map(value => options.find(option => option.id === value)!);
    setSelectedOptions(selected);
  };

  return (
    <FormControl fullWidth>
      <InputLabel id="multiple-select-label">Select</InputLabel>
      <Select
        labelId="multiple-select-label"
        id="multiple-select"
        multiple
        value={selectedOptions.map(option => option.id)}
        onChange={handleChange}
        renderValue={(selected) => (
        <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
          {selected.map((roleId) => (
            <Chip key={roleId} label={options.find((o) => o.id === roleId)!.name} />
          ))}
        </Box>
      )}
      >
        {options.map(option => (
          <MenuItem key={option.id} value={option.id}>
            {option.name}
          </MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

export default MultipleSelect;
Engen answered 28/2 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.