selectCreatableField.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import styled from '@emotion/styled';
  2. import {StyledForm} from 'sentry/components/deprecatedforms/form';
  3. import SelectField from 'sentry/components/deprecatedforms/selectField';
  4. import SelectControl from 'sentry/components/forms/controls/selectControl';
  5. import {SelectValue} from 'sentry/types';
  6. import {defined} from 'sentry/utils';
  7. import convertFromSelect2Choices from 'sentry/utils/convertFromSelect2Choices';
  8. // XXX: This is ONLY used in GenericField. If we can delete that this can go.
  9. /**
  10. * @deprecated Do not use this
  11. *
  12. * This is a <SelectField> that allows the user to create new options if one does't exist.
  13. *
  14. * This is used in some integrations
  15. */
  16. export default class SelectCreatableField extends SelectField {
  17. options: SelectValue<any>[] | undefined;
  18. constructor(props, context) {
  19. super(props, context);
  20. // We only want to parse options once because react-select relies
  21. // on `options` mutation when you create a new option
  22. //
  23. // Otherwise you will not get the created option in the dropdown menu
  24. this.options = this.getOptions(props);
  25. }
  26. UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
  27. const newError = this.getError(nextProps, nextContext);
  28. if (newError !== this.state.error) {
  29. this.setState({error: newError});
  30. }
  31. if (this.props.value !== nextProps.value || defined(nextContext.form)) {
  32. const newValue = this.getValue(nextProps, nextContext);
  33. // This is the only thing that is different from parent, we compare newValue against coerced value in state
  34. // To remain compatible with react-select, we need to store the option object that
  35. // includes `value` and `label`, but when we submit the format, we need to coerce it
  36. // to just return `value`. Also when field changes, it propagates the coerced value up
  37. const coercedValue = this.coerceValue(this.state.value);
  38. // newValue can be empty string because of `getValue`, while coerceValue needs to return null (to differentiate
  39. // empty string from cleared item). We could use `!=` to compare, but lets be a bit more explicit with strict equality
  40. //
  41. // This can happen when this is apart of a field, and it re-renders onChange for a different field,
  42. // there will be a mismatch between this component's state.value and `this.getValue` result above
  43. if (
  44. newValue !== coercedValue &&
  45. !!newValue !== !!coercedValue &&
  46. newValue !== this.state.value
  47. ) {
  48. this.setValue(newValue);
  49. }
  50. }
  51. }
  52. getOptions(props) {
  53. return convertFromSelect2Choices(props.choices) || props.options;
  54. }
  55. getField() {
  56. const {placeholder, disabled, clearable, name} = this.props;
  57. return (
  58. <StyledSelectControl
  59. creatable
  60. id={this.getId()}
  61. options={this.options}
  62. placeholder={placeholder}
  63. disabled={disabled}
  64. value={this.state.value}
  65. onChange={this.onChange}
  66. clearable={clearable}
  67. multiple={this.isMultiple()}
  68. name={name}
  69. />
  70. );
  71. }
  72. }
  73. // This is because we are removing `control-group` class name which provides margin-bottom
  74. const StyledSelectControl = styled(SelectControl)`
  75. ${StyledForm} &, .form-stacked & {
  76. .control-group & {
  77. margin-bottom: 0;
  78. }
  79. margin-bottom: 15px;
  80. }
  81. `;