selectAsyncField.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import {Component} from 'react';
  2. import InputField, {InputFieldProps} from 'sentry/components/forms/inputField';
  3. import SelectAsyncControl, {
  4. Result,
  5. SelectAsyncControlProps,
  6. } from 'sentry/components/forms/selectAsyncControl';
  7. // projects can be passed as a direct prop as well
  8. import {GeneralSelectValue} from 'sentry/components/forms/selectControl';
  9. export interface SelectAsyncFieldProps
  10. extends Omit<InputFieldProps, 'highlighted' | 'visible' | 'required' | 'value'>,
  11. SelectAsyncControlProps {
  12. /**
  13. * Similar to onChange, except it provides the entire option object (including label) when a
  14. * change is made to the field. Occurs after onChange.
  15. */
  16. onChangeOption?: (option: GeneralSelectValue, event: any) => void;
  17. }
  18. type SelectAsyncFieldState = {
  19. results: Result[];
  20. latestSelection?: GeneralSelectValue;
  21. };
  22. class SelectAsyncField extends Component<SelectAsyncFieldProps, SelectAsyncFieldState> {
  23. state: SelectAsyncFieldState = {
  24. results: [],
  25. latestSelection: undefined,
  26. };
  27. componentDidMount() {}
  28. // need to map the option object to the value
  29. // this is essentially the same code from ./selectField handleChange()
  30. handleChange = (
  31. onBlur: SelectAsyncFieldProps['onBlur'],
  32. onChange: SelectAsyncFieldProps['onChange'],
  33. onChangeOption: SelectAsyncFieldProps['onChangeOption'],
  34. optionObj: GeneralSelectValue,
  35. event: React.MouseEvent
  36. ) => {
  37. let {value} = optionObj;
  38. if (!optionObj) {
  39. value = optionObj;
  40. } else if (this.props.multiple && Array.isArray(optionObj)) {
  41. // List of optionObjs
  42. value = optionObj.map(({value: val}) => val);
  43. } else if (!Array.isArray(optionObj)) {
  44. value = optionObj.value;
  45. }
  46. this.setState({latestSelection: optionObj});
  47. onChange?.(value, event);
  48. onChangeOption?.(optionObj, event);
  49. onBlur?.(value, event);
  50. };
  51. findValue(propsValue: string): GeneralSelectValue {
  52. const {defaultOptions} = this.props;
  53. const {results, latestSelection} = this.state;
  54. // We don't use defaultOptions if it is undefined or a boolean
  55. const options = typeof defaultOptions === 'object' ? defaultOptions : [];
  56. /**
  57. * The propsValue is the `id` of the object (user, team, etc), and
  58. * react-select expects a full value object: {value: "id", label: "name"}
  59. **/
  60. return (
  61. // When rendering the selected value, first look at the API results...
  62. results.find(({value}) => value === propsValue) ??
  63. // Then at the defaultOptions passed in props...
  64. options?.find(({value}) => value === propsValue) ??
  65. // Then at the latest value selected in the form
  66. (latestSelection as GeneralSelectValue)
  67. );
  68. }
  69. render() {
  70. const {onChangeOption, ...otherProps} = this.props;
  71. return (
  72. <InputField
  73. {...otherProps}
  74. field={({onBlur, onChange, required: _required, onResults, value, ...props}) => (
  75. <SelectAsyncControl
  76. {...props}
  77. onChange={this.handleChange.bind(this, onBlur, onChange, onChangeOption)}
  78. onResults={data => {
  79. const results = onResults(data);
  80. const resultSelection = results.find(result => result.value === value);
  81. this.setState(
  82. resultSelection ? {results, latestSelection: resultSelection} : {results}
  83. );
  84. return results;
  85. }}
  86. onSelectResetsInput
  87. onCloseResetsInput={false}
  88. onBlurResetsInput={false}
  89. value={this.findValue(value)}
  90. />
  91. )}
  92. />
  93. );
  94. }
  95. }
  96. export default SelectAsyncField;