最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Multiselect in a dropdown list in React - Stack Overflow

programmeradmin0浏览0评论

I am trying to be able to multi select different values from a dropdown I created. So the following code is actually showing the names of the people but I would like to be able to select more than one.

<Form.Group controlId="exampleForm.ControlSelect4">
    <Form.Label> Names </Form.Label>
    <Form.Control as="select" value={this.state.selectedNames} 
                  onChange={this.updateTable}>
        {this.state.names.map((name) => <option key={name.value} value={name.value}> {name.display } </option>)}
    </Form.Control>
</Form.Group>

I am trying to be able to multi select different values from a dropdown I created. So the following code is actually showing the names of the people but I would like to be able to select more than one.

<Form.Group controlId="exampleForm.ControlSelect4">
    <Form.Label> Names </Form.Label>
    <Form.Control as="select" value={this.state.selectedNames} 
                  onChange={this.updateTable}>
        {this.state.names.map((name) => <option key={name.value} value={name.value}> {name.display } </option>)}
    </Form.Control>
</Form.Group>
Share Improve this question edited Jun 25, 2020 at 18:02 JuanCaicedo 3,4583 gold badges20 silver badges37 bronze badges asked Jun 19, 2020 at 4:34 user234user234 591 gold badge1 silver badge5 bronze badges 1
  • Are you using react-bootstrap? – JuanCaicedo Commented Jun 20, 2020 at 21:00
Add a ment  | 

3 Answers 3

Reset to default 1

It is similar to setting a single value but instead, the value is an array instead of a string or a number.

First, you have to change what the value and onChange function are doing. For the value, set the default state as an array. For the onChange, we are going to set it where whenever the item is checked, it sets a new state so like this:

javascript

state = {
 selectedNames:[]
}

onChange = (e) => {
  e.preventDefault()
  this.setState(prevState => ({selectedNames: [...prevState.selectedNames, e.target.value]})
}

Hopefully, this helps!

The event needs to be added to individual option, multi select takes quite a bit of lines to implement. Here's some snippet just for the sections you might care. I'm not using any third party controls as you can see.

      <div className="_action">
        <span
          role="button"
          aria-pressed="false"
          tabIndex={0}
          onClick={() => { onClear() }}
        >
          Clear Selection
        </span>
      </div>
      {options.map(option => (
        <div
          role="presentation"
          className="_item"
          key={option.value}
          onClick={() => { onSelect(option.value) }}
        >
          <Checkbox value={isChecked(option, value)} />
          <span className="_label">{option.label}</span>
        </div>
      ))}

The onSelect and onClear might be provided from parent/self ponent,

  const onSelect = useCallback(v => {
    const e = {
      target: {
        name,
        value: toggleValueInOptions(value, v, options)
      }
    }
    onChange(e)
  }, [name, value, options, onChange])

  const onClear = useCallback(() => {
    const e = { target: { name, value: [] } }
    onChange(e)
  }, [name, onChange])

and a utility function toggleValueiInOptions

const toggleValueInOptions = (value, key, options) => {
  if (!value) return []

  const values = value.slice()
  const index = values.indexOf(key)
  if (index >= 0) {
    values.splice(index, 1)
  } else {
    values.push(key)
  }

  if (!options) return values

  return options.reduce((acc, option) => {
    if (values.includes(option.value)) {
      acc.push(option.value)
    }
    return acc
  }, [])
}

export default toggleValueInOptions

==============

For your reference, this is the plete code for the parent MultiSelect.

import React, { useState, useCallback, useRef } from 'react'
import PropTypes from 'prop-types'
import { useClickOutside } from '../../utils'
import InputBase from '../InputBase'
import Pills from './Pills'
import MultiSelection from './MultiSelection'
import MultiSelectStyle from './MultiSelectStyle'
import SelectIcon from './SelectIcon'
import { optionsType, valuesType } from './optionsType'
import toggleValueInOptions from './toggleValueInOptions'
import valueToItems from './valueToItems'
import SelectionSummary from './SelectionSummary'

/**
 * @memberof MultiSelect
 * @param {Object} _                Props
 * @param {elementType} _.Style       Style ponent
 * @param {string} _.name             Input name
 * @param {valueType[]} _.value         Input value of array
 * @param {func} _.onChange           Value change event
 * @param {optionsType[]} _.options     Options array
 * @param {elementType} _.Selection=MultiSelection    Component for dropdown selection
 * @param {bool} _.disabled=false     Input disabled flag
 * @param {bool} _.width=auto         Input width
 * @param {string} _.placeholder      Input placeholder
 * @param {elementType} _.DropdownIcon=DropdownIcon   Compoent for dropdown icon ponent
 * @param {number} _.pillVisibleMax   Max pill displayed
 * @param {elementType} _.Summary=SelectionSummary    Component for dropdown summary
 */
const MultiSelect = ({
  Style, name, value, options, onChange,
  Selection, disabled, width, placeholder,
  DropdownIcon, pillVisibleMax, Summary,
  ...props
}) => {
  const [focus, setFocus] = useState(false)

  const onExpand = useCallback(() => {
    if (!disabled) setFocus(true)
  }, [disabled])
  const onCollapse = useCallback(() => { setFocus(false) }, [])
  const ref = useRef()
  useClickOutside({ ref, handler: () => { onCollapse() } })

  const onSelect = useCallback(v => {
    const e = {
      target: {
        name,
        value: toggleValueInOptions(value, v, options)
      }
    }
    onChange(e)
  }, [name, value, options, onChange])

  const onClear = useCallback(() => {
    const e = { target: { name, value: [] } }
    onChange(e)
  }, [name, onChange])

  const after = <DropdownIcon focus={focus} onExpand={onExpand} onCollapse={onCollapse} />

  const phText = value.length ? '' : placeholder
  const vText = (value.length > pillVisibleMax) ? `${value.length} Selected` : ''

  return (
    <Style ref={ref}>
      <InputBase
        value={vText}
        placeholder={phText}
        disabled={disabled}
        readOnly
        after={after}
        onFocus={onExpand}
        width={width}
        {...props}
      />
      {!vText && (
        <Pills
          items={valueToItems(value, options)}
          onSelect={onSelect}
          disabled={disabled}
        />
      )}
      {focus && (
        <Selection
          value={value}
          options={options}
          onSelect={onSelect}
          onClear={onClear}
          Summary={Summary}
        />
      )}
    </Style>
  )
}

MultiSelect.propTypes = {
  Style: PropTypes.elementType,
  name: PropTypes.string,
  value: valuesType,
  options: optionsType,
  onChange: PropTypes.func,
  Selection: PropTypes.elementType,
  disabled: PropTypes.bool,
  width: PropTypes.string,
  placeholder: PropTypes.string,
  DropdownIcon: PropTypes.elementType,
  pillVisibleMax: PropTypes.number,
  Summary: PropTypes.elementType
}

MultiSelect.defaultProps = {
  Style: MultiSelectStyle,
  name: '',
  value: [],
  options: [],
  onChange: () => { },
  Selection: MultiSelection,
  disabled: false,
  width: '',
  placeholder: '',
  DropdownIcon: SelectIcon,
  pillVisibleMax: 99,
  Summary: SelectionSummary
}

export default MultiSelect

(I'm assuming you are using react-bootstrap)

Answer

You will need to pass another prop to Form.Control to specify that you would like your select to allow multiple selections. You can do this either as multiple={true}, or as the shorthand multiple (both are equivalent).

This example in their docs uses a multiselect, that might be helpful.

I put together this sandbox which might illustrate how to use it.

What's tricky

Handling state with react can be hard. Forms are notoriously challenging because they involve a lot of internal state.

Other things to try

  • Use react hooks to manage state instead of a class ponent. This can make it easier to work with state, in my opinion

Bug in my example

  • When there is only one selection and you try to deselect it, the onChange event is not trigged. I'm not sure why that's happening here
发布评论

评论列表(0)

  1. 暂无评论