Source: app/enhanced-redux-form/enhancedForm.js

/* global WP_DEFINE_DEVELOPMENT */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import reduxForm from 'redux-form/lib/reduxForm';
import { connect } from 'react-redux';
import { validateForm, registerForm, submitForm } from './actions/enhancedFormActions';
import { wizardStepMount } from './actions/wizardFormActions';

/** @module enhanced-redux-form */
const enhancedForm = (reduxFormConfig, validation) => WrappedComponent => {
  class EnhancedForm extends Component {
    componentDidMount() {
      const { enhancedFormWizard } = this.context;

      if (WP_DEFINE_DEVELOPMENT) {
        if (
          enhancedFormWizard &&
          (typeof reduxFormConfig.destroyOnUnmount === 'undefined' ||
            reduxFormConfig.destroyOnUnmount)
        ) {
          throw new Error(
            'enhancedReduxForm should be passed {destroyOnUnmount: false} if part of an enhancedFormWizard',
          );
        }
      }

      this.props.registerForm();

      if (enhancedFormWizard) {
        // please note: router may be undefined react-router is not used.
        const { router } = this.context;
        const { routingAdapter, name: wizardName } = enhancedFormWizard;

        this.props.wizardStepMount(wizardName, routingAdapter, router);
      }
    }

    componentDidUpdate(prevProps) {
      if (this.props.shouldValidate.length) {
        prevProps.validateForm(this.props.shouldValidate);
      }
    }

    submit = event => {
      if (event) {
        event.preventDefault();
      }
      this.props.submitForm(this.context.enhancedFormWizard);
    };

    render() {
      /* eslint-disable no-unused-vars */
      const {
        // we create variables here so they are excluded from 'formProps'
        validateForm: validateFormProp,
        dispatch: dispatchProp,
        wizardStepMount: wizardStepMountProp,
        generalError,
        submitForm: submitFormProp,
        registerForm: registerFormProp,
        onSubmit: onSubmitProp,
        shouldValidate: shouldValidateProp,
        ...formProps
      } = this.props;
      /* eslint-enable no-unused-vars */

      return (
        <WrappedComponent
          {...formProps}
          submitForm={null}
          shouldValidate={null}
          registerForm={null}
          error={generalError}
          handleSubmit={this.submit}
        />
      );
    }
  }

  EnhancedForm.displayName = `enhancedReduxForm(${WrappedComponent.displayName ||
    WrappedComponent.name ||
    'Component'})`;

  EnhancedForm.propTypes = {
    /* eslint-disable react/no-unused-prop-types */
    /*
     * Note: normally we don't use dispatch directly inside our component. This is an exception
     * because we need to dispatch dynamic actions from the wizard routing adapter.
     */
    dispatch: PropTypes.func.isRequired,
    form: PropTypes.string.isRequired,
    /* eslint-enable react/no-unused-prop-types */
    registerForm: PropTypes.func.isRequired,
    wizardStepMount: PropTypes.func.isRequired,
    validateForm: PropTypes.func.isRequired,
    submitForm: PropTypes.func.isRequired,
    generalError: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.shape({
        locale: PropTypes.string.isRequired,
        params: PropTypes.objectOf(PropTypes.any),
      }),
    ]),
    // eslint-disable-next-line react/no-unused-prop-types
    onSubmit: PropTypes.func,
    // eslint-disable-next-line react/no-unused-prop-types
    shouldValidate: PropTypes.arrayOf(PropTypes.string).isRequired,
  };

  EnhancedForm.contextTypes = {
    enhancedFormWizard: PropTypes.shape({
      name: PropTypes.string.isRequired,
      routingAdapter: PropTypes.object.isRequired,
    }),
    router: PropTypes.shape({
      location: PropTypes.shape({
        pathname: PropTypes.string.isRequired,
      }).isRequired,
    }),
  };

  return connect(
    (state, props) => {
      const validationState = state.enhancedForm?.validation[props.form];

      return {
        shouldValidate: validationState ? validationState.shouldValidate || [] : [],
        generalError: validationState ? validationState.generalError : null,
      };
    },
    (dispatch, props) => ({
      validateForm: fields => dispatch(validateForm(validation, props.form, fields)),
      registerForm: () => dispatch(registerForm(props.form, validation)),
      wizardStepMount: (wizardName, routingAdapter, router) =>
        dispatch(wizardStepMount(props.form, wizardName, routingAdapter, router)),
      submitForm: enhancedFormWizard =>
        dispatch(
          submitForm({
            validation,
            form: props.form,
            formProps: props,
            submitHandler: props.onSubmit || reduxFormConfig.onSubmit,
            transformApiFieldNames:
              props.transformApiFieldNames || reduxFormConfig.transformApiFieldNames,
            submitSuccessHandler: props.onSubmitSuccess || reduxFormConfig.onSubmitSuccess,
            submitFailHandler: props.onSubmitFail || reduxFormConfig.onSubmitFail,
            generalErrorMessage: props.generalErrorMessage || reduxFormConfig.generalErrorMessage,
            wizardRoutingAdapter: enhancedFormWizard && enhancedFormWizard.routingAdapter,
            isWizardStep: reduxFormConfig.isWizardStep,
          }),
        ),
    }),
  )(EnhancedForm);
};

/**
 * @name ValidationRule
 * @property {function} rule A function that returns true or false for valid or invalid. May
 * also return a promise that resolves with true or false. It will receive the following arguments:
 *  - **value** The current value of the field
 *  - **values** An object containing all the values in the form
 *  - **name** The name of the field that is being validated
 * @property {string|Object} [message] A message that should be set as error message when
 * this field is invalid. Can be either a string, or an object with the following properties:
 *  - **locale** The id of the locale string to lookup
 *  - **params** (optional) The MessageFormat parameters to pass to the locale string
 * @property {string} [code] A unique string to identify the error. Can be used to implement
 * custom error messages
 */

/**
 * @name ValidationConfig
 * @type {object}
 * @property {string|Array<string>} [validateOn] A string or array of strings that specifies
 * on which event the validation should trigger. See ValidateOn.js for possible values.
 * @property {Array<module:enhanced-redux-form~ValidationRule>} validators An array of validation
 * rules to test this field for.
 */

/**
 * Returns a function that wraps a component in enhanced-redux-form functionality. Syntax
 * is similar to the reduxForm() method of redux-form:
 *
 * `const MyEnhancedForm = enhancedReduxForm(reduxFormConfig, validation)(MyForm);`
 *
 * @default
 * @function enhancedReduxForm
 * @tutorial enhanced-redux-form
 * @param {Object} reduxFormConfig The configuration object that will be passed to redux-form. The
 * enhanced configuration object allows the following additional properties:
 * @param {Object} [reduxFormConfig.wizard] If passed, indicates that this form is part of a
 * multi-step wizard form
 * @param {number} [reduxFormConfig.wizard.index] The index of this step in the wizard
 * @param {string} [reduxFormConfig.wizard.route] The route that this form will be available under.
 * When an error occurs in the submission, we will navigate to this route
 * @param {string} [reduxFormConfig.transformApiFieldNames] A function that maps each field
 * name in an API validation response to a field name in this form. Can be used to map flattened
 * field names in the API back to nested names in redux-form.
 * @param [Object<string, module:enhanced-redux-form~ValidationConfig>] validation The validation
 * configuration object. The keys of this object correspond to names of fields in the form, and the
 * values are the corresponding validation configs.
 * @returns {function} The function that wraps a component in an enhanced component with
 * enhanced-redux-form functionality
 * @category forms
 */
export default (reduxFormConfig, validation) => component =>
  reduxForm(reduxFormConfig)(enhancedForm(reduxFormConfig, validation)(component));