function createReduxForm(structure: Structure<any, any>) {
  const { deepEqual, empty, getIn, setIn, keys, fromJS, toJS } = structure
  const isValid = createIsValid(structure)
  return (initialConfig: Config) => {
    const config = {
      touchOnBlur: true,
      touchOnChange: false,
      persistentSubmitErrors: false,
      destroyOnUnmount: true,
      shouldAsyncValidate: defaultShouldAsyncValidate,
      shouldValidate: defaultShouldValidate,
      shouldError: defaultShouldError,
      shouldWarn: defaultShouldWarn,
      enableReinitialize: false,
      keepDirtyOnReinitialize: false,
      updateUnregisteredFields: false,
      getFormState: state => getIn(state, 'form'),
      pure: true,
      forceUnregisterOnUnmount: false,
      submitAsSideEffect: false,
      ...initialConfig
    }

    return (WrappedComponent: ComponentType<any>) => {
      class Form extends React.Component<PropsWithContext> {
        static WrappedComponent: ComponentType<any>

        wrapped: ElementRef<any> = React.createRef()

        destroyed = false
        fieldCounts = {}
        fieldValidators = {}
        lastFieldValidatorKeys = []
        fieldWarners = {}
        lastFieldWarnerKeys = []
        innerOnSubmit = undefined
        submitPromise = undefined
        initializedOnLoad = false

        constructor(...args) {
          super(...args)
          if (!isHotReloading()) {
            this.initializedOnLoad = this.initIfNeeded()
          }
          invariant(
            this.props.shouldValidate,
            'shouldValidate() is deprecated and will be removed in v9.0.0. Use shouldWarn() or shouldError() instead.'
          )
        }

        initIfNeeded = (nextProps: ?PropsWithContext): boolean => {
          const { enableReinitialize } = this.props
          if (nextProps) {
            if (
              (enableReinitialize || !nextProps.initialized) &&
              !deepEqual(this.props.initialValues, nextProps.initialValues)
            ) {
              const keepDirty = nextProps.initialized && this.props.keepDirtyOnReinitialize
              this.props.initialize(nextProps.initialValues, keepDirty, {
                keepValues: nextProps.keepValues,
                lastInitialValues: this.props.initialValues,
                updateUnregisteredFields: nextProps.updateUnregisteredFields
              })
              return true
            }
          } else if (this.props.initialValues && (!this.props.initialized || enableReinitialize)) {
            this.props.initialize(this.props.initialValues, this.props.keepDirtyOnReinitialize, {
              keepValues: this.props.keepValues,
              updateUnregisteredFields: this.props.updateUnregisteredFields
            })
            return true
          }
          return false
        }

        updateSyncErrorsIfNeeded = (
          nextSyncErrors: ?Object,
          nextError: ?any,
          lastSyncErrors: ?Object
        ): void => {
          const { error, updateSyncErrors } = this.props
          const noErrors = (!lastSyncErrors || !Object.keys(lastSyncErrors).length) && !error
          const nextNoErrors =
            (!nextSyncErrors || !Object.keys(nextSyncErrors).length) && !nextError
          if (
            !(noErrors && nextNoErrors) &&
            (!plain.deepEqual(lastSyncErrors, nextSyncErrors) || !plain.deepEqual(error, nextError))
          ) {
            updateSyncErrors(nextSyncErrors, nextError)
          }
        }

        clearSubmitPromiseIfNeeded = (nextProps: PropsWithContext): void => {
          const { submitting } = this.props
          if (this.submitPromise && submitting && !nextProps.submitting) {
            delete this.submitPromise
          }
        }

        submitIfNeeded = (nextProps: PropsWithContext): void => {
          const { clearSubmit, triggerSubmit } = this.props
          if (!triggerSubmit && nextProps.triggerSubmit) {
            clearSubmit()
            this.submit()
          }
        }

        shouldErrorFunction = (): ShouldValidateFunction | ShouldErrorFunction => {
          const { shouldValidate, shouldError } = this.props
          const shouldValidateOverridden = shouldValidate !== defaultShouldValidate
          const shouldErrorOverridden = shouldError !== defaultShouldError

          return shouldValidateOverridden && !shouldErrorOverridden ? shouldValidate : shouldError
        }

        validateIfNeeded = (nextProps: ?PropsWithContext): void => {
          const { validate, values } = this.props
          const shouldError = this.shouldErrorFunction()
          const fieldLevelValidate = this.generateValidator()
          if (validate || fieldLevelValidate) {
            const initialRender = nextProps === undefined
            const fieldValidatorKeys = Object.keys(this.getValidators())
            const validateParams = {
              values,
              nextProps,
              props: this.props,
              initialRender,
              lastFieldValidatorKeys: this.lastFieldValidatorKeys,
              fieldValidatorKeys,
              structure
            }

            if (shouldError(validateParams)) {
              const propsToValidate = initialRender || !nextProps ? this.props : nextProps
              const { _error, ...nextSyncErrors } = merge(
                validate ? validate(propsToValidate.values, propsToValidate) || {} : {},
                fieldLevelValidate
                  ? fieldLevelValidate(propsToValidate.values, propsToValidate) || {}
                  : {}
              )
              this.lastFieldValidatorKeys = fieldValidatorKeys
              this.updateSyncErrorsIfNeeded(nextSyncErrors, _error, propsToValidate.syncErrors)
            }
          } else {
            this.lastFieldValidatorKeys = []
          }
        }

        updateSyncWarningsIfNeeded = (
          nextSyncWarnings: ?Object,
          nextWarning: any,
          lastSyncWarnings: ?Object
        ): void => {
          const { warning, updateSyncWarnings } = this.props
          const noWarnings =
            (!lastSyncWarnings || !Object.keys(lastSyncWarnings).length) && !warning
          const nextNoWarnings =
            (!nextSyncWarnings || !Object.keys(nextSyncWarnings).length) && !nextWarning
          if (
            !(noWarnings && nextNoWarnings) &&
            (!plain.deepEqual(lastSyncWarnings, nextSyncWarnings) ||
              !plain.deepEqual(warning, nextWarning))
          ) {
            updateSyncWarnings(nextSyncWarnings, nextWarning)
          }
        }

        shouldWarnFunction = (): ShouldValidateFunction | ShouldWarnFunction => {
          const { shouldValidate, shouldWarn } = this.props
          const shouldValidateOverridden = shouldValidate !== defaultShouldValidate
          const shouldWarnOverridden = shouldWarn !== defaultShouldWarn

          return shouldValidateOverridden && !shouldWarnOverridden ? shouldValidate : shouldWarn
        }

        warnIfNeeded = (nextProps: ?PropsWithContext): void => {
          const { warn, values } = this.props
          const shouldWarn = this.shouldWarnFunction()
          const fieldLevelWarn = this.generateWarner()
          if (warn || fieldLevelWarn) {
            const initialRender = nextProps === undefined
            const fieldWarnerKeys = Object.keys(this.getWarners())
            const validateParams = {
              values,
              nextProps,
              props: this.props,
              initialRender,
              lastFieldValidatorKeys: this.lastFieldWarnerKeys,
              fieldValidatorKeys: fieldWarnerKeys,
              structure
            }

            if (shouldWarn(validateParams)) {
              const propsToWarn = initialRender || !nextProps ? this.props : nextProps
              const { _warning, ...nextSyncWarnings } = merge(
                warn ? warn(propsToWarn.values, propsToWarn) : {},
                fieldLevelWarn ? fieldLevelWarn(propsToWarn.values, propsToWarn) : {}
              )
              this.lastFieldWarnerKeys = fieldWarnerKeys
              this.updateSyncWarningsIfNeeded(nextSyncWarnings, _warning, propsToWarn.syncWarnings)
            }
          }
        }

        UNSAFE_componentWillReceiveProps(nextProps: PropsWithContext): void {
          const isValueReset = this.initIfNeeded(nextProps)
          // initialize will dispatch a redux action and call componentWillReceiveProps again; hence we can skip reinitialize if needed.
          if (isValueReset) return
          this.validateIfNeeded(nextProps)
          this.warnIfNeeded(nextProps)
          this.clearSubmitPromiseIfNeeded(nextProps)
          this.submitIfNeeded(nextProps)
          const { onChange, values, dispatch } = nextProps
          if (onChange && !deepEqual(values, this.props.values)) {
            onChange(values, dispatch, nextProps, this.props.values)
          }
        }

        shouldComponentUpdate(nextProps: PropsWithContext): boolean {
          if (!this.props.pure) return true
          const { immutableProps = [] } = config
          // if we have children, we MUST update in React 16
          // https://twitter.com/erikras/status/915866544558788608
          return !!(
            this.props.children ||
            nextProps.children ||
            Object.keys(nextProps).some(prop => {
              // useful to debug rerenders
              // if (!plain.deepEqual(this.props[ prop ], nextProps[ prop ])) {
              //   console.info(prop, 'changed', this.props[ prop ], '==>', nextProps[ prop ])
              // }
              if (~immutableProps.indexOf(prop)) {
                return this.props[prop] !== nextProps[prop]
              }
              return (
                !~propsToNotUpdateFor.indexOf(prop) && !deepEqual(this.props[prop], nextProps[prop])
              )
            })
          )
        }

        componentDidMount(): void {
          if (!isHotReloading()) {
            // initialize in constructor function will dispatch a redux action and call componentWillReceiveProps which checks for validate;
            // hence we can skip validate and warning if initialize has been triggered in constructor
            if (this.initializedOnLoad) return
            this.validateIfNeeded()
            this.warnIfNeeded()
          }
          invariant(
            this.props.shouldValidate,
            'shouldValidate() is deprecated and will be removed in v9.0.0. Use shouldWarn() or shouldError() instead.'
          )
        }

        componentWillUnmount(): void {
          const { destroyOnUnmount, destroy } = this.props
          if (destroyOnUnmount && !isHotReloading()) {
            this.destroyed = true
            destroy()
          }
        }

        getValues = (): Values => this.props.values

        isValid = (): boolean => this.props.valid

        isPristine = (): boolean => this.props.pristine

        register = (
          name: string,
          type: FieldType,
          getValidator: Function,
          getWarner: Function
        ): void => {
          const lastCount = this.fieldCounts[name]
          const nextCount = (lastCount || 0) + 1
          this.fieldCounts[name] = nextCount
          this.props.registerField(name, type)
          if (getValidator) {
            this.fieldValidators[name] = getValidator
          }
          if (getWarner) {
            this.fieldWarners[name] = getWarner
          }
        }

        unregister = (name: string): void => {
          const lastCount = this.fieldCounts[name]
          if (lastCount === 1) delete this.fieldCounts[name]
          else if (lastCount != null) this.fieldCounts[name] = lastCount - 1

          if (!this.destroyed) {
            const { destroyOnUnmount, forceUnregisterOnUnmount, unregisterField } = this.props
            if (destroyOnUnmount || forceUnregisterOnUnmount) {
              unregisterField(name, destroyOnUnmount)
              if (!this.fieldCounts[name]) {
                delete this.fieldValidators[name]
                delete this.fieldWarners[name]
                this.lastFieldValidatorKeys = this.lastFieldValidatorKeys.filter(
                  key => key !== name
                )
              }
            } else {
              unregisterField(name, false)
            }
          }
        }

        getFieldList = (options: Object): string[] => {
          let registeredFields = this.props.registeredFields
          if (!registeredFields) {
            return []
          }
          let keySeq = keys(registeredFields)
          if (options) {
            if (options.excludeFieldArray) {
              keySeq = keySeq.filter(
                name => getIn(registeredFields, `['${name}'].type`) !== 'FieldArray'
              )
            }
            if (options.excludeUnregistered) {
              keySeq = keySeq.filter(name => getIn(registeredFields, `['${name}'].count`) !== 0)
            }
          }
          return toJS(keySeq)
        }

        getValidators = (): Object => {
          const validators = {}
          Object.keys(this.fieldValidators).forEach(name => {
            const validator = this.fieldValidators[name]()
            if (validator) {
              validators[name] = validator
            }
          })
          return validators
        }

        generateValidator = (): Function | void => {
          const validators = this.getValidators()
          return Object.keys(validators).length
            ? generateValidator(validators, structure)
            : undefined
        }

        getWarners = (): Object => {
          const warners = {}
          Object.keys(this.fieldWarners).forEach(name => {
            const warner = this.fieldWarners[name]()
            if (warner) {
              warners[name] = warner
            }
          })
          return warners
        }

        generateWarner = (): Function | void => {
          const warners = this.getWarners()
          return Object.keys(warners).length ? generateValidator(warners, structure) : undefined
        }

        asyncValidate = (
          name: string,
          value: any,
          trigger: 'blur' | 'change'
        ): Promise<void> | void => {
          const {
            asyncBlurFields,
            asyncChangeFields,
            asyncErrors,
            asyncValidate,
            dispatch,
            initialized,
            pristine,
            shouldAsyncValidate,
            startAsyncValidation,
            stopAsyncValidation,
            syncErrors,
            values
          } = this.props
          const submitting = !name

          const fieldNeedsValidation = () => {
            const fieldNeedsValidationForBlur =
              asyncBlurFields && name && ~asyncBlurFields.indexOf(name.replace(/\[[0-9]+]/g, '[]'))
            const fieldNeedsValidationForChange =
              asyncChangeFields &&
              name &&
              ~asyncChangeFields.indexOf(name.replace(/\[[0-9]+]/g, '[]'))
            const asyncValidateByDefault = !(asyncBlurFields || asyncChangeFields)

            return (
              submitting ||
              asyncValidateByDefault ||
              (trigger === 'blur' ? fieldNeedsValidationForBlur : fieldNeedsValidationForChange)
            )
          }

          if (asyncValidate) {
            const valuesToValidate = submitting ? values : setIn(values, name, value)
            const syncValidationPasses = submitting || !getIn(syncErrors, name)
            if (
              fieldNeedsValidation() &&
              shouldAsyncValidate({
                asyncErrors,
                initialized,
                trigger: submitting ? 'submit' : trigger,
                blurredField: name,
                pristine,
                syncValidationPasses
              })
            ) {
              return asyncValidation(
                () => asyncValidate(valuesToValidate, dispatch, this.props, name),
                startAsyncValidation,
                stopAsyncValidation,
                name
              )
            }
          }
        }

        submitCompleted = (result: any): any => {
          delete this.submitPromise
          return result
        }

        submitFailed = (error: any): void => {
          delete this.submitPromise
          throw error
        }

        listenToSubmit = (promise: any): Promise<any> => {
          if (!isPromise(promise)) {
            return promise
          }
          this.submitPromise = promise
          return promise.then(this.submitCompleted, this.submitFailed)
        }
The decorator that is the main API to redux-form