See also:
Article

Multi-<select> in React

How to implement a controlled multi-<select> in React?
A device which has been pulled apart

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.

Last updated on 12/13/2019 by Anton Vasetenkov.