In a react app, you should control a <select>
. It's real easy with a single select:
const ExampleSelect = () => {
const [value, setValue] = React.useState()
return (
<select
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
>
<option value='foo'>Foo</option>
<option value='bar'>Bar</option>
<option value='baz'>Baz</option>
</select>
)
}
But when you add the multiple
prop to a <select>
things get weird. Functions passed to onChange
that iterate through the options and take actions based on which ones have option.selected === true
only seem to work on desktop -- in Safari on iOS, a select with multiple === true
seems impossible to control.
I feel like this is a really basic question, but I can't find anything on it. I'm not looking to install a whole library that comes with pre-styled components just to be able to select multiple options in a <select>
inside a react app.
Here are a few onChange functions I've tried:
type Setter = React.Dispatch<React.SetStateAction<string[] | undefined>>
type SelectChange = React.ChangeEvent<HTMLSelectElement>
export const handleMultiSelectChange = <S extends Function = Setter>(
value: string[] | undefined, setter: S
) => (
e: SelectChange
): void => {
const v = e.currentTarget.value
if (value && value.length > 0) {
const index = value.indexOf(v)
if (index >= 0) {
const newValue = value.filter((opt) => opt !== v)
setter(newValue)
} else {
setter([...value, v])
}
} else {
setter([v])
}
}
type Setter = React.Dispatch<React.SetStateAction<string[] | undefined>>
type SelectChange = React.ChangeEvent<HTMLSelectElement>
export const handleChangeMultiple = <S extends Function = Setter>(
value: string[] | undefined = [], setter: S
) => (
event: SelectChange
): void => {
const {options} = event.currentTarget
for (let i = 0, l = options.length; i < l; i += 1) {
const option = options[i]
if (option.selected) {
alert(value.includes(option.value))
if (value.includes(option.value)) {
const index = value.indexOf(option.value)
value.splice(index, 1)
} else {
value.push(option.value)
}
}
}
setter(value)
}
(those ones are used like this:)
const ExampleSelect = () => {
const [value, setValue] = React.useState()
return (
<select
multiple
value={value}
onChange={handleChangeMultiple(value, setValue)}
>
<option value='foo'>Foo</option>
<option value='bar'>Bar</option>
<option value='baz'>Baz</option>
</select>
)
}
(though that's not working correctly for me)
For select, e.target
gives you options
array and you can loop thru it getselected
option. Use map
to grab the actual values and store them in your state.
select
<select
onChange={handleChangeNormalSelect}
multiple
value={val}
options={options}
>
{options.map(item => {
return <option value={item.value}>{item.label}</option>;
})}
</select>
onChange
...
const [val, setVal] = useState([]);
...
const handleChangeNormalSelect = e => {
const updatedOptions = [...e.target.options]
.filter(option => option.selected)
.map(x => x.value);
console.log("updatedOptions", updatedOptions);
setVal(updatedOptions);
};
...