/* 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));