I'm trying to create a Select
component with a series of custom items that are mapped through a list. Each item has a specific type and, based on that type, the menu item will have a certain MUI icon. I created a specific component for managing the whole Select
component and another specific component to display each item, its value and its icon. The problem is that the onChange
function is not triggered whenever I click on one of the items. What could the problem be?
I'm attaching a code of what I'm doing.
const myTypes = {
TYPE_1: "Type 1",
TYPE_2: "Type 2",
TYPE_3: "Type 3",
};
function TypeSelect(props) {
const classes = useStyles();
const [state, setState] = useState(myTypes.TYPE_1);
const onChangeType = (e) => {
setState(e.target.value);
};
return (
<Select
id="select-type"
value={state}
onChange={onChangeType}
>
{Object.keys(myTypes).map((type) => (
<TypeSelectMenuItem
type={myTypes[type]}
key={type}
/>
))}
</Select>
);
}
function TypeSelectMenuItem({ type }) {
const renderIcon = () => {
switch (type) {
case myTypes.TYPE_1:
return <ShortTextIcon />; // Material-UI icon
case myTypes.TYPE_2:
return <SubjectIcon />; // Material-UI icon
case myTypes.TYPE_3:
return <RadioButtonCheckedIcon />; // Material-UI icon
default:
return <Fragment />;
}
};
return (
<MenuItem value={type} >
<ListItemIcon>{renderIcon()}</ListItemIcon>
<ListItemText primary={type} />
</MenuItem>
);
}
I'm trying to create a Select
component with a series of custom items that are mapped through a list. Each item has a specific type and, based on that type, the menu item will have a certain MUI icon. I created a specific component for managing the whole Select
component and another specific component to display each item, its value and its icon. The problem is that the onChange
function is not triggered whenever I click on one of the items. What could the problem be?
I'm attaching a code of what I'm doing.
const myTypes = {
TYPE_1: "Type 1",
TYPE_2: "Type 2",
TYPE_3: "Type 3",
};
function TypeSelect(props) {
const classes = useStyles();
const [state, setState] = useState(myTypes.TYPE_1);
const onChangeType = (e) => {
setState(e.target.value);
};
return (
<Select
id="select-type"
value={state}
onChange={onChangeType}
>
{Object.keys(myTypes).map((type) => (
<TypeSelectMenuItem
type={myTypes[type]}
key={type}
/>
))}
</Select>
);
}
function TypeSelectMenuItem({ type }) {
const renderIcon = () => {
switch (type) {
case myTypes.TYPE_1:
return <ShortTextIcon />; // Material-UI icon
case myTypes.TYPE_2:
return <SubjectIcon />; // Material-UI icon
case myTypes.TYPE_3:
return <RadioButtonCheckedIcon />; // Material-UI icon
default:
return <Fragment />;
}
};
return (
<MenuItem value={type} >
<ListItemIcon>{renderIcon()}</ListItemIcon>
<ListItemText primary={type} />
</MenuItem>
);
}
Share
Improve this question
edited Nov 1, 2021 at 2:16
NearHuscarl
81.4k22 gold badges318 silver badges280 bronze badges
asked Apr 4, 2021 at 16:14
FsannaFsanna
3771 gold badge5 silver badges13 bronze badges
1
- Can you paste the import statements too. – dileep nandanam Commented Apr 5, 2021 at 1:10
1 Answer
Reset to default 16According to Material-UI docs, this is how you use the MenuItem
component to render Select
's options:
<Select value={age} onChange={handleChange}>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
When you want to pass a custom component instead of MenuItem
as Select
children item. For example TypeSelectMenuItem
, you need to do the following things:
1. Pass all props to your custom item component
SelectInput
will clone every child and attach additional event handlers as extra props behind the scene, so make sure to pass all of them down using spread operator:
const TypeSelectMenuItem = (props) => {
return (
<MenuItem {...props}>
{...}
</MenuItem>
);
};
<Select value={state} onChange={onChangeType}>
{Object.keys(myTypes).map((type) => (
<TypeSelectMenuItem value={myTypes[type]} key={type}>
{myTypes[type]}
</TypeSelectMenuItem>
))}
</Select>
2. Pass the option value to the value
props
Do not invent a new props name like what you did here:
<TypeSelectMenuItem type={myTypes[type]} key={type} />
The correct way is to pass to value
instead of type
props:
<TypeSelectMenuItem value={myTypes[type]} key={type} />
The reason is because internally, SelectInput
references props.value
instead of your props.type
. See this and this. Using anything other than props.value
will lead to unexpected behaviors.
3. Reference data-value
instead of value
in your custom component
When cloning item elements, SelectInput
set the value
props to undefined and utilize data-value
props instead, So you need to use data-value
props internally:
const TypeSelectMenuItem = (props) => {
// when passing as children item of Select. props.value is set to undefined.
// Use props['data-value'] instead.
return (
<MenuItem {...props}>
{...}
<ListItemText primary={props["data-value"]} />
</MenuItem>
);
};
4. Pass children to your custom item component to render selected value
SelectInput
uses children props to render selected value, so you need to make sure to pass something as children to display:
const TypeSelectMenuItem = (props) => {
return (
<MenuItem {...props}>
// this is children of MenuItem, so it is not displayed as selected value
<ListItemIcon>{renderIcon()}</ListItemIcon>
<ListItemText primary={props["data-value"]} />
</MenuItem>
);
};
<Select value={state} onChange={onChangeType}>
{Object.keys(myTypes).map((type) => (
<TypeSelectMenuItem value={myTypes[type]} key={type}>
// this is children of TypeSelectMenuItem, so it is displayed as selected value
{myTypes[type]}
</TypeSelectMenuItem>
))}
</Select>