Repository: h2oai/wave
File: ui/src/combobox.tsx (TypeScript)

Code before:
  width?: S
  /** True if the component should be visible. Defaults to True. */
  visible?: B
  /** An optional tooltip message displayed when a user clicks the help icon to the right of the component. */
  tooltip?: S
  /** True if the choice should be submitted when an item from the dropdown is selected or the textbox value changes. */
  trigger?: B
  /** True if this is a required field. Defaults to False. */
  required?: B
}

export const
  XCombobox = ({ model: m }: { model: Combobox }) => {
    const
      isMultiValued = !!m.values,
      [text, setText] = React.useState(m.value),
      [options, setOptions] = React.useState((m.choices || []).map((text): Fluent.IComboBoxOption => ({ key: text, text }))),
      getInitialSelected = () => 
        (m.values || []).reduce((acc: Fluent.IComboBoxOption[], cur: S) => {
          const found = options.find(option => option.text === cur)
          return found ? [...acc, found] : acc
        }, []),
      [selected, setSelected] = React.useState(getInitialSelected()),
      onChange = (_e: React.FormEvent<Fluent.IComboBox>, option?: Fluent.IComboBoxOption, _index?: U, value?: S) => {
        let selected = option?.selected
        const v = option?.text || value || ''
        
        if (!option && value) {
          selected = true
          option = { key: value, text: value }
          setOptions((prevOptions = []) => [...prevOptions, option!])
        }

        if (option && isMultiValued) {
          setSelected(items => {
            const result = selected ? [...items, option as Fluent.IComboBoxOption] : items.filter(item => item.key !== option!.key)
            wave.args[m.name] = result.map(item => item!.text)
            return result
          })
        } else {
          wave.args[m.name] = v
          setText(v)
        }
        
        if (m.trigger) wave.push()
      }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useEffect(() => { wave.args[m.name] = m?.value || m?.values || null }, [])

    return (
      <Fluent.ComboBox

Review comment (suggestion):
Let's go with something easier to follow:

```ts
      selectOpt = (option: Fluent.IComboBoxOption) => {
        setSelected(items => {
          const result = option.selected ? [...items, option] : items.filter(item => item.key !== option!.key)
          wave.args[m.name] = result.map(item => item.text)
          return result
        })
      },
      onChange = (_e: React.FormEvent<Fluent.IComboBox>, option?: Fluent.IComboBoxOption, _idx?: U, value = '') => {
        if (!option && value) {
          const opt: Fluent.IComboBoxOption = { key: value, text: value, selected: true }
          setOptions((prevOptions = []) => [...prevOptions, opt])
          selectOpt(opt)
        }
        if (option && isMultiValued) {
          selectOpt(option)
        }
        else {
          const v = option?.text || value
          wave.args[m.name] = v
          setText(v)
        }

        if (m.trigger) wave.push()
      }
```

Code after:
  width?: S
  /** True if the component should be visible. Defaults to True. */
  visible?: B
  /** An optional tooltip message displayed when a user clicks the help icon to the right of the component. */
  tooltip?: S
  /** True if the choice should be submitted when an item from the dropdown is selected or the textbox value changes. */
  trigger?: B
  /** True if this is a required field. Defaults to False. */
  required?: B
}

export const
  XCombobox = ({ model: m }: { model: Combobox }) => {
    const
      isMultiValued = !!m.values,
      [text, setText] = React.useState(m.value),
      [options, setOptions] = React.useState((m.choices || []).map((text): Fluent.IComboBoxOption => ({ key: text, text }))),
      [selected, setSelected] = React.useState<Fluent.IComboBoxOption[]>((m.values || []).map(text => ({ key: text, text }))),
      selectOpt = (option: Fluent.IComboBoxOption) => {
        setSelected(items => {
          const result = option.selected ? [...items, option] : items.filter(item => item.key !== option.key)
          wave.args[m.name] = result.map(item => item.text)
          return result
        })
      },
      onChange = (_e: React.FormEvent<Fluent.IComboBox>, option?: Fluent.IComboBoxOption, _index?: U, value?: S) => {
        if (!option && value) {
          const opt: Fluent.IComboBoxOption = { key: value, text: value, selected: true }
          setOptions((prevOptions = []) => [...prevOptions, opt])
          selectOpt(opt)
        }
        if (option && isMultiValued) {
          selectOpt(option)
        }
        else {
          const v = option?.text || value || ''
          wave.args[m.name] = v
          setText(v)
        }
        if (m.trigger) wave.push()
      }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    React.useEffect(() => { wave.args[m.name] = m?.value || m?.values || null }, [])

    return (
      <Fluent.ComboBox
        data-test={m.name}
        label={m.label}
        placeholder={m.placeholder}
        options={options}
        required={m.required}