apiForm.tsx 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {Children} from 'react';
  2. import {
  3. addErrorMessage,
  4. addLoadingMessage,
  5. clearIndicators,
  6. } from 'sentry/actionCreators/indicator';
  7. import {APIRequestMethod, Client} from 'sentry/api';
  8. import Form from 'sentry/components/deprecatedforms/form';
  9. import FormField from 'sentry/components/deprecatedforms/formField';
  10. import FormState from 'sentry/components/forms/state';
  11. import {t} from 'sentry/locale';
  12. type Props = Form['props'] & {
  13. apiEndpoint: string;
  14. apiMethod: APIRequestMethod;
  15. omitDisabled?: boolean;
  16. onSubmit?: (data: object) => void;
  17. submitErrorMessage?: string;
  18. submitLoadingMessage?: string;
  19. };
  20. export default class ApiForm extends Form<Props> {
  21. api = new Client();
  22. static defaultProps = {
  23. ...Form.defaultProps,
  24. omitDisabled: false, // TODO(chadwhitacre) Upstream, flip to true, deprecate.
  25. submitErrorMessage: t('There was an error saving your changes.'),
  26. submitLoadingMessage: t('Saving changes\u2026'),
  27. };
  28. componentWillUnmount() {
  29. this.api.clear();
  30. }
  31. getEnabledData() {
  32. // Return a hash of data from non-disabled fields.
  33. // Start with this.state.data and remove rather than starting from scratch
  34. // and adding, because a) this.state.data is our source of truth, and b)
  35. // we'd have to do more work to loop over the state.data Object and lookup
  36. // against the props.children Array (looping over the Array and looking up
  37. // in the Object is more natural). Maybe the consequent use of delete
  38. // carries a slight performance hit. Why is yer form so big? 🤔
  39. const data = {...this.state.data}; // Copy to avoid mutating state.data itself.
  40. Children.forEach(this.props.children, (child: any) => {
  41. if (!FormField.isPrototypeOf(child.type)) {
  42. return; // Form children include h4's, etc.
  43. }
  44. if (child.key && child.props?.disabled) {
  45. delete data[child.key]; // Assume a link between child.key and data. 🐭
  46. }
  47. });
  48. return data;
  49. }
  50. onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  51. e.preventDefault();
  52. if (this.state.state === FormState.SAVING) {
  53. return;
  54. }
  55. // Actual HTML forms do not submit data for disabled fields, and because of
  56. // the way some of our APIs are implemented, we need to start doing the
  57. // same. But, since some other parts of the app very probably depend on
  58. // sending disabled fields, keep that the default for now.
  59. // TODO(chadwhitacre): Expand and upstream this.
  60. const data = this.props.omitDisabled ? this.getEnabledData() : this.state.data;
  61. this.props.onSubmit && this.props.onSubmit(data);
  62. this.setState(
  63. {
  64. state: FormState.SAVING,
  65. },
  66. () => {
  67. addLoadingMessage(this.props.submitLoadingMessage);
  68. this.api.request(this.props.apiEndpoint, {
  69. method: this.props.apiMethod,
  70. data,
  71. success: result => {
  72. clearIndicators();
  73. this.onSubmitSuccess(result);
  74. },
  75. error: error => {
  76. addErrorMessage(this.props.submitErrorMessage);
  77. this.onSubmitError(error);
  78. },
  79. });
  80. }
  81. );
  82. };
  83. }