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

Code before:
        const result = option.selected ? [...keys, String(option.key)] : keys.filter(key => key !== option.key)
        wave.args[m.name] = result
        return result
      })
    },
    onChange = (_e: React.FormEvent<IComboBox>, option?: IComboBoxOption, _index?: U, value?: S) => {
      if (!option && value) {
        const opt: IComboBoxOption = { key: value, text: value }
        setOptions((prevOptions = []) => [...prevOptions, opt])
        selectOpt({...opt, selected: true})
      }
      if (option) {
        selectOpt(option)
      }
      if (m.trigger) wave.push()
    }
  
  React.useEffect(() => {
    wave.args[m.name] = m.values?.length ? m.values : null
    setSelected(m.values || [])
    setOptions((prevOptions = []) =>
      [
        ...prevOptions,
        ...(m.values || []).filter(v => !prevOptions.map(o => String(o.key)).includes(v)).map(v => ({ key: v, text: v }))
      ]
    )
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [m.values])

  return (
    <Fluent.ComboBox
      data-test={m.name}
      label={m.label}
      placeholder={m.placeholder}
      options={options}
      required={m.required}
      multiSelect
      selectedKey={selected}
      allowFreeform
      disabled={m.disabled}
      autoComplete="on"
      errorMessage={m.error}
      onChange={onChange}
    />
  )
}

const useOptions = (choices: S[] = []): [Fluent.IComboBoxOption[], React.Dispatch<React.SetStateAction<Fluent.IComboBoxOption[]>>]  => {
  const mapChoices = React.useMemo(() => (choices || []).map((text): Fluent.IComboBoxOption => ({ key: text, text })), [choices])
  const [options, setOptions] = React.useState(mapChoices)
  

Review comment (refactor):
I guess this is necessary only if `m.values` are not empty, right? Also a minor refactoring suggestion.
```suggestion
    if (m.values?.length) {
      setOptions((prevOptions = []) =>
        [
          ...prevOptions,
          ...m.values!.filter(v => !prevOptions.some(o => o.key === v)).map(v => ({ key: v, text: v }))
        ]
      )
    }
```

Code after:
        wave.args[m.name] = result
        return result
      })
    },
    onChange = (_e: React.FormEvent<IComboBox>, option?: IComboBoxOption, _index?: U, value?: S) => {
      if (!option && value) {
        const opt: IComboBoxOption = { key: value, text: value }
        setOptions((prevOptions = []) => [...prevOptions, opt])
        selectOpt({ ...opt, selected: true })
      }
      if (option) {
        selectOpt(option)
      }
      if (m.trigger) wave.push()
    }

  React.useEffect(() => {
    wave.args[m.name] = m.values?.length ? m.values : null
    setSelected(m.values || [])
    if (m.values?.length) {
      setOptions((prevOptions = []) =>
        [
          ...prevOptions,
          ...m.values!.filter(v => !prevOptions.some(o => o.key === v)).map(v => ({ key: v, text: v }))
        ]
      )
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [m.values])

  return (
    <Fluent.ComboBox
      data-test={m.name}
      label={m.label}
      placeholder={m.placeholder}
      options={options}
      required={m.required}
      multiSelect
      selectedKey={selected}
      allowFreeform
      disabled={m.disabled}
      autoComplete="on"
      errorMessage={m.error}
      onChange={onChange}
    />
  )
}

const useOptions = (choices: S[] = []): [Fluent.IComboBoxOption[], React.Dispatch<React.SetStateAction<Fluent.IComboBoxOption[]>>] => {
  const mappedChoices = React.useMemo(() => (choices || []).map((text): Fluent.IComboBoxOption => ({ key: text, text })), [choices])
  const [options, setOptions] = React.useState(mappedChoices)