Source: app/enhanced-redux-form/components/CompositeInput.js

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import FormSection from 'redux-form/lib/FormSection';
import { connect } from 'react-redux';
import {
  registerCompositeInput,
  unregisterCompositeInput,
  composeCompositeInputs,
  decomposeCompositeInput,
} from '../actions/compositeInputActions';
import compositeInputFormatters from '../compositeInputFormatters';
import { COMPOSITE_FORMSECTION_NAME_PREFIX } from '../data/constants';
import * as ComposeOn from '../data/ComposeOn';

/**
 * @module enhanced-redux-form/components/CompositeInput
 */

/* eslint-disable consistent-return */
const validateFormatterProp = (props, propName, componentName) => {
  const formatter = props[propName];

  if (!formatter.name || !formatter.formatter) {
    return new Error(
      `Invalid prop '${propName}' supplied to '${componentName}'. ${propName} should be of shape {name:string, formatter:<formatter config>}`,
    );
  }

  if (!compositeInputFormatters[formatter.name]) {
    return new Error(
      `Invalid prop '${propName}' supplied to '${componentName}'. ${formatter.name} does not exist in compositeInputFormatters.js`,
    );
  }
};
/* eslint-enable consistent-return */

/* eslint-disable no-underscore-dangle */

/**
 * For documentation, please refer to the tutorial:
 * {@tutorial enhanced-redux-form-composite-inputs}
 *
 * @class CompositeInput
 * @category forms
 * @tutorial enhanced-redux-form-composite-inputs
 */
class CompositeInput extends PureComponent {
  componentDidMount() {
    const { formatter, composeOn, value } = this.props;
    const { form, register } = this.context._reduxForm;
    const name = this.getName();
    register(name, 'Field');
    this.props.registerCompositeInput(form, name, { formatter, composeOn });

    if (value) {
      this.props.decomposeCompositeInput(form, name);
    }
  }

  componentDidUpdate(prevProps) {
    if (prevProps.name !== this.props.name || prevProps.formatter !== this.props.formatter) {
      const { form, unregister, register } = this.context._reduxForm;
      const currentName = this.getName();
      const newName = this.getName(this.props);

      unregister(currentName);
      this.props.unregisterCompositeInput(form, currentName);

      register(newName, 'Field');
      this.props.registerCompositeInput(form, newName, {
        formatter: this.props.formatter,
        composeOn: this.props.composeOn,
      });

      prevProps.composeCompositeInputs(form, [newName]);
    } else if (prevProps.value !== this.props.value) {
      prevProps.decomposeCompositeInput(this.props.form, this.getName(this.props));
    }
  }

  componentWillUnmount() {
    const { form } = this.context._reduxForm;
    const name = this.getName();

    this.context._reduxForm.unregister(name);
    this.props.unregisterCompositeInput(form, name);
  }

  /**
   * Get the prefixed name based on the name prop and the context. Based on the prefixName.js
   * util in redux-form.
   * @param {object} props? The current props of the component. Defaults to this.props
   * @returns {string} The prefixed name
   */
  getName(props = this.props) {
    const { sectionPrefix } = this.context._reduxForm;
    const { name } = props;
    const isFieldArrayRegx = /\[\d+]$/;

    return !sectionPrefix || isFieldArrayRegx.test(name) ? name : `${sectionPrefix}.${name}`;
  }

  render() {
    const { name, children } = this.props;

    return (
      <FormSection name={`${COMPOSITE_FORMSECTION_NAME_PREFIX}${name}`}>{children}</FormSection>
    );
  }
}

CompositeInput.propTypes = {
  /*
   * The name of the combined input
   */
  name: PropTypes.string.isRequired,
  /*
   * The value of this CompositeInput in the redux state. Passed by the connect() wrapper
   */
  value: PropTypes.any,
  /*
   * The name of the wrapping form. Passed by the CompositeInputWrapper
   */
  form: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
  /*
   * A formatter definition to use to combine the input values into a single composite
   * value. Should be one of the formatters defined in 'compositeInputFormatters.js'
   */
  formatter: validateFormatterProp,
  /*
   * Prop that defines on which events this CompositeInput should run the formatter function
   * to combine the child field values into a single value. Should be one of the values
   * from composeOn.js
   * If not provided, will default to ComposeOn.VALIDATE  (null)
   */
  composeOn: PropTypes.oneOf([
    ComposeOn.VALIDATE_AND_BLUR,
    ComposeOn.VALIDATE_AND_CHANGE,
    ComposeOn.VALIDATE_AND_FOCUS,
  ]),
  registerCompositeInput: PropTypes.func.isRequired,
  unregisterCompositeInput: PropTypes.func.isRequired,
  decomposeCompositeInput: PropTypes.func.isRequired,
  composeCompositeInputs: PropTypes.func.isRequired, // eslint-disable-line react/no-unused-prop-types
  children: PropTypes.node,
};

CompositeInput.contextTypes = {
  _reduxForm: PropTypes.object.isRequired,
};

const ConnectedCompositeInput = connect(
  (state, { name, form }) => ({
    value: state.form?.[form]?.values?.[name],
  }),
  {
    registerCompositeInput,
    unregisterCompositeInput,
    composeCompositeInputs,
    decomposeCompositeInput,
  },
)(CompositeInput);

/* eslint-disable no-underscore-dangle */
const CompositeInputWrapper = (props, context) => (
  <ConnectedCompositeInput {...props} form={context._reduxForm.form} />
);

CompositeInputWrapper.contextTypes = {
  _reduxForm: PropTypes.object.isRequired,
};
/* eslint-enable no-underscore-dangle */

export default CompositeInputWrapper;