selectAsyncField.tsx 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import {useState} from 'react';
  2. import SelectAsyncControl, {
  3. Result,
  4. SelectAsyncControlProps,
  5. } from 'sentry/components/forms/controls/selectAsyncControl';
  6. // projects can be passed as a direct prop as well
  7. import {GeneralSelectValue} from 'sentry/components/forms/controls/selectControl';
  8. import FormField from 'sentry/components/forms/formField';
  9. // XXX(epurkhiser): This is wrong, it should not be inheriting these props
  10. import {InputFieldProps} from './inputField';
  11. export interface SelectAsyncFieldProps
  12. extends Omit<InputFieldProps, 'highlighted' | 'visible' | 'required' | 'value'>,
  13. SelectAsyncControlProps {
  14. /**
  15. * Similar to onChange, except it provides the entire option object (including label) when a
  16. * change is made to the field. Occurs after onChange.
  17. */
  18. onChangeOption?: (option: GeneralSelectValue, event: any) => void;
  19. }
  20. function SelectAsyncField({onChangeOption, ...props}: SelectAsyncFieldProps) {
  21. const [results, setResults] = useState<Result[]>([]);
  22. const [latestSelection, setLatestSelection] = useState<
  23. GeneralSelectValue | undefined
  24. >();
  25. return (
  26. <FormField {...props}>
  27. {({
  28. required: _required,
  29. children: _children,
  30. onBlur,
  31. onChange,
  32. onResults,
  33. value,
  34. ...fieldProps
  35. }) => {
  36. const {defaultOptions} = props;
  37. // We don't use defaultOptions if it is undefined or a boolean
  38. const options = typeof defaultOptions === 'object' ? defaultOptions : [];
  39. // The propsValue is the `id` of the object (user, team, etc), and
  40. // react-select expects a full value object: {value: "id", label: "name"}
  41. const resolvedValue =
  42. // When rendering the selected value, first look at the API results...
  43. results.find(({value: v}) => v === value) ??
  44. // Then at the defaultOptions passed in props...
  45. options?.find(({value: v}) => v === value) ??
  46. // Then at the latest value selected in the form
  47. (latestSelection as GeneralSelectValue);
  48. return (
  49. <SelectAsyncControl
  50. {...fieldProps}
  51. onChange={(option, e) => {
  52. const resultValue = !option
  53. ? option
  54. : props.multiple && Array.isArray(option)
  55. ? // List of optionObjs
  56. option.map(({value: val}) => val)
  57. : !Array.isArray(option)
  58. ? option.value
  59. : option;
  60. setLatestSelection(option);
  61. onChange?.(resultValue, e);
  62. onChangeOption?.(option, e);
  63. onBlur?.(resultValue, e);
  64. }}
  65. onResults={data => {
  66. const newResults = onResults(data);
  67. const resultSelection = newResults.find(result => result.value === value);
  68. setResults(newResults);
  69. if (resultSelection) {
  70. setLatestSelection(resultSelection);
  71. }
  72. return newResults;
  73. }}
  74. onSelectResetsInput
  75. onCloseResetsInput={false}
  76. onBlurResetsInput={false}
  77. value={resolvedValue}
  78. />
  79. );
  80. }}
  81. </FormField>
  82. );
  83. }
  84. export default SelectAsyncField;