Unlike the regular <select>
, multi-<select>
has more complex onChange
logic:
type Value = any
type MultiSelectProps = {
options: {
label: string
value: Value
}[]
values: Value[]
onChange: (values: Value[]) => void
}
const MultiSelect = ({ options, values, onChange }: MultiSelectProps) => (
<select
multiple={true}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedOptions = options.filter((option, optionIndex) => event.target.options[optionIndex].selected)
onChange(selectedOptions.map(({ value }) => value))
}}
defaultValue={[]}
>
{options.map(({ label, value }) => (
<option key={value} value={value} selected={values.includes(value)}>{label}</option>
))}
</select>
)
If the Value
type is string
, the onChange
prop passed to <select>
can be simplified:
type MultiSelectProps = {
options: {
label: string
value: string
}[]
values: string[]
onChange: (values: string[]) => void
}
const MultiSelect = ({ options, values, onChange }: MultiSelectProps) => (
<select
multiple={true}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedOptions = [...event.target.selectedOptions]
onChange(selectedOptions.map(({ value }) => value))}
}
defaultValue={[]}
>
{options.map(({ label, value }) => (
<option key={value} value={value} selected={values.includes(value)}>{label}</option>
))}
</select>
)
Note that HTMLSelectElement.selectedOptions
is not supported in Internet Explorer ≤ 11. Use this line instead:
const selectedOptions = [...event.target.options].filter(({ selected }) => selected)
It still requires transpilation because it uses ES2015's const
, array spread, and parameter destructuring syntax.