src/createReduxForm.js
// @flow
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'
import isPromise from 'is-promise'
import { mapValues, merge } from 'lodash'
import PropTypes from 'prop-types'
import React, { createElement } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import importedActions from './actions'
import asyncValidation from './asyncValidation'
import defaultShouldAsyncValidate from './defaultShouldAsyncValidate'
import defaultShouldValidate from './defaultShouldValidate'
import defaultShouldError from './defaultShouldError'
import defaultShouldWarn from './defaultShouldWarn'
import silenceEvent from './events/silenceEvent'
import silenceEvents from './events/silenceEvents'
import generateValidator from './generateValidator'
import handleSubmit from './handleSubmit'
import createIsValid from './selectors/isValid'
import plain from './structure/plain'
import getDisplayName from './util/getDisplayName'
import isHotReloading from './util/isHotReloading'
import { withReduxForm, ReduxFormContext } from './ReduxFormContext'
import type { ComponentType, Node, ElementRef } from 'react'
import type { Dispatch } from 'redux'
import type { ReactContext, GetFormState, FieldType, Structure, Values } from './types'
import type { Params as ShouldAsyncValidateParams } from './defaultShouldAsyncValidate'
import type { Params as ShouldValidateParams } from './defaultShouldValidate'
import type { Params as ShouldErrorParams } from './defaultShouldError'
import type { Params as ShouldWarnParams } from './defaultShouldWarn'
const isClassComponent = (Component: ?any) =>
Boolean(
Component && Component.prototype && typeof Component.prototype.isReactComponent === 'object'
)
// extract field-specific actions
const {
arrayInsert,
arrayMove,
arrayPop,
arrayPush,
arrayRemove,
arrayRemoveAll,
arrayShift,
arraySplice,
arraySwap,
arrayUnshift,
blur,
change,
focus,
...formActions
} = importedActions
const arrayActions = {
arrayInsert,
arrayMove,
arrayPop,
arrayPush,
arrayRemove,
arrayRemoveAll,
arrayShift,
arraySplice,
arraySwap,
arrayUnshift
}
const propsToNotUpdateFor = [
...Object.keys(importedActions),
'array',
'asyncErrors',
'initialValues',
'syncErrors',
'syncWarnings',
'values',
'registeredFields'
]
const checkSubmit = submit => {
if (!submit || typeof submit !== 'function') {
throw new Error(
'You must either pass handleSubmit() an onSubmit function or pass onSubmit as a prop'
)
}
return submit
}
type OnSubmitFail = (
errors: ?Object,
dispatch: Dispatch<any>,
submitError: ?any,
props: Object
) => void
type OnSubmitSuccess = (result: ?any, dispatch: Dispatch<any>, props: Object) => void
type InitializeAction = (initialValues: ?Values, keepDirty: boolean, otherMeta: ?Object) => void
type FocusAction = (field: string) => void
type ChangeAction = (field: string, value: any) => void
type BlurAction = (field: string, value: any) => void
type ArrayUnshiftAction = (field: string, value: any) => void
type ArrayShiftAction = (field: string) => void
type ArraySpliceAction = (field: string, index: number, removeNum: number, value: any) => void
type ArrayInsertAction = (field: string, index: number, value: any) => void
type ArrayMoveAction = (field: string, from: number, to: number) => void
type ArrayPopAction = (field: string) => void
type ArrayPushAction = (field: string, value: any) => void
type ArrayRemoveAction = (field: string, index: number) => void
type ArrayRemoveAllAction = (field: string) => void
type ArraySwapAction = (field: string, indexA: number, indexB: number) => void
type ClearSubmitAction = () => void
type DestroyAction = () => void
type RegisterFieldAction = (name: string, type: FieldType) => void
type UnregisterFieldAction = (name: string, destroyOnUnmount: ?boolean) => void
type ResetAction = () => void
type ResetSectionAction = () => void
type SetSubmitFailedAction = (...fields: string[]) => void
type SetSubmitSucceededAction = (...fields: string[]) => void
type StartAsyncValidationAction = (field: string) => void
type StopAsyncValidationAction = (errors: ?Object) => void
type StopSubmitAction = (errors: ?Object) => void
type StartSubmitAction = () => void
type TouchAction = (...fields: string[]) => void
type UntouchAction = (...fields: string[]) => void
type UpdateSyncErrorsAction = (syncErrors: ?Object, error: ?any) => void
type UpdateSyncWarningsAction = (syncErrors: ?Object, error: ?any) => void
type OnSubmitFunction = (
values: Values,
dispatch: Dispatch<any>,
props: Object
) => Promise<any> | void
type AsyncValidateFunction = (
values: Values,
dispatch: Dispatch<any>,
props: Object,
blurredField: ?string
) => Promise<void>
type ValidateFunction = (values: Values, props: Object) => Object
type ShouldAsyncValidateFunction = (params: ShouldAsyncValidateParams) => boolean
type ShouldValidateFunction = (params: ShouldValidateParams) => boolean
type ShouldErrorFunction = (params: ShouldErrorParams) => boolean
type ShouldWarnFunction = (params: ShouldWarnParams) => boolean
type OnChangeFunction = (
values: Values,
dispatch: Dispatch<any>,
props: Object,
previousValues: Values
) => void
type RequiredConfig = {
form: string
}
type DefaultedConfig = {
destroyOnUnmount: boolean,
enableReinitialize: boolean,
getFormState: GetFormState,
forceUnregisterOnUnmount: boolean,
keepDirtyOnReinitialize: boolean,
persistentSubmitErrors: boolean,
pure: boolean,
shouldAsyncValidate: ShouldAsyncValidateFunction,
shouldValidate: ShouldValidateFunction,
shouldError: ShouldErrorFunction,
shouldWarn: ShouldWarnFunction,
submitAsSideEffect: boolean,
touchOnBlur: boolean,
touchOnChange: boolean,
updateUnregisteredFields: boolean
}
type OptionalConfig = {
asyncBlurFields?: string[],
asyncChangeFields?: string[],
asyncValidate?: AsyncValidateFunction,
keepValues?: boolean,
immutableProps?: string[],
initialValues?: Values,
onChange?: OnChangeFunction,
onSubmit?: OnSubmitFunction,
onSubmitFail?: OnSubmitFail,
onSubmitSuccess?: OnSubmitSuccess,
propNamespace?: string,
validate?: ValidateFunction,
warn?: ValidateFunction
}
// the options that users pass in to the @reduxForm decorator, or other decorators created with createReduxForm()
export type Config = RequiredConfig & $Shape<DefaultedConfig> & OptionalConfig
// props passed in to the internal ReduxForm React component; all the values in Config, with defaults filled in, plus
// all redux-derived values and action creators
export type Props = RequiredConfig &
DefaultedConfig &
OptionalConfig & {
array: Object,
anyTouched: boolean,
arrayInsert: ArrayInsertAction,
arrayMove: ArrayMoveAction,
arrayPop: ArrayPopAction,
arrayPush: ArrayPushAction,
arrayRemove: ArrayRemoveAction,
arrayRemoveAll: ArrayRemoveAllAction,
arrayShift: ArrayShiftAction,
arraySplice: ArraySpliceAction,
arraySwap: ArraySwapAction,
arrayUnshift: ArrayUnshiftAction,
asyncChangeFields?: string[],
asyncErrors?: any,
asyncValidating: boolean,
blur: BlurAction,
change: ChangeAction,
children: Node,
clearSubmit: ClearSubmitAction,
destroy: DestroyAction,
dirty: boolean,
dispatch: Dispatch<any>,
error?: any,
focus: FocusAction,
getFormState: GetFormState,
initialize: InitializeAction,
initialized: boolean,
invalid: boolean,
updateUnregisteredFields: boolean,
onChange?: OnChangeFunction,
onSubmit?: OnSubmitFunction,
onSubmitFail?: OnSubmitFail,
onSubmitSuccess?: OnSubmitSuccess,
pristine: boolean,
propNamespace?: string,
pure?: boolean,
registeredFields: Array<{ name: string, type: FieldType, count: number }>,
registerField: RegisterFieldAction,
reset: ResetAction,
resetSection: ResetSectionAction,
setSubmitFailed: SetSubmitFailedAction,
setSubmitSucceeded: SetSubmitSucceededAction,
shouldAsyncValidate: ShouldAsyncValidateFunction,
shouldValidate: ShouldValidateFunction,
startAsyncValidation: StartAsyncValidationAction,
startSubmit: StartSubmitAction,
stopAsyncValidation: StopAsyncValidationAction,
stopSubmit: StopSubmitAction,
submitAsSideEffect: boolean,
submitting: boolean,
submitFailed: boolean,
submitSucceeded: boolean,
touch: TouchAction,
touchOnBlur: boolean,
touchOnChange: boolean,
triggerSubmit?: boolean,
persistentSubmitErrors: boolean,
syncErrors?: Object,
syncWarnings?: Object,
unregisterField: UnregisterFieldAction,
untouch: UntouchAction,
updateSyncErrors: UpdateSyncErrorsAction,
updateSyncWarnings: UpdateSyncWarningsAction,
valid: boolean,
validate: ValidateFunction,
validExceptSubmit: boolean,
values: Values,
warn: ValidateFunction,
warning: any
}
type PropsWithContext = { _reduxForm?: ReactContext } & Props
/**
* The decorator that is the main API to redux-form
*/
export default 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)
}
submit = (submitOrEvent: any): any => {
const { onSubmit, blur, change, dispatch } = this.props
if (!submitOrEvent || silenceEvent(submitOrEvent)) {
// submitOrEvent is an event: fire submit if not already submitting
if (!this.submitPromise) {
// avoid recursive stack trace if use Form with onSubmit as handleSubmit
if (this.innerOnSubmit && this.innerOnSubmit !== this.submit) {
// will call "submitOrEvent is the submit function" block below
return this.innerOnSubmit()
} else {
return this.listenToSubmit(
handleSubmit(
checkSubmit(onSubmit),
({
...this.props,
...bindActionCreators({ blur, change }, dispatch)
}: any), // TODO: fix type, should be `Props`
this.props.validExceptSubmit,
this.asyncValidate,
this.getFieldList({
excludeFieldArray: true,
excludeUnregistered: true
})
)
)
}
}
} else {
// submitOrEvent is the submit function: return deferred submit thunk
return silenceEvents(() => {
return (
!this.submitPromise &&
this.listenToSubmit(
handleSubmit(
checkSubmit(submitOrEvent),
({
...this.props,
...bindActionCreators({ blur, change }, dispatch)
}: any), // TODO: fix type, should be `Props`
this.props.validExceptSubmit,
this.asyncValidate,
this.getFieldList({
excludeFieldArray: true,
excludeUnregistered: true
})
)
)
)
})
}
}
reset = (): void => this.props.reset()
render() {
// remove some redux-form config-only props
/* eslint-disable no-unused-vars */
const {
anyTouched,
array,
arrayInsert,
arrayMove,
arrayPop,
arrayPush,
arrayRemove,
arrayRemoveAll,
arrayShift,
arraySplice,
arraySwap,
arrayUnshift,
asyncErrors,
asyncValidate,
asyncValidating,
blur,
change,
clearSubmit,
destroy,
destroyOnUnmount,
forceUnregisterOnUnmount,
dirty,
dispatch,
enableReinitialize,
error,
focus,
form,
getFormState,
immutableProps,
initialize,
initialized,
initialValues,
invalid,
keepDirtyOnReinitialize,
keepValues,
updateUnregisteredFields,
pristine,
propNamespace,
registeredFields,
registerField,
reset,
resetSection,
setSubmitFailed,
setSubmitSucceeded,
shouldAsyncValidate,
shouldValidate,
shouldError,
shouldWarn,
startAsyncValidation,
startSubmit,
stopAsyncValidation,
stopSubmit,
submitAsSideEffect,
submitting,
submitFailed,
submitSucceeded,
touch,
touchOnBlur,
touchOnChange,
persistentSubmitErrors,
syncErrors,
syncWarnings,
unregisterField,
untouch,
updateSyncErrors,
updateSyncWarnings,
valid,
validExceptSubmit,
values,
warning,
...rest
} = this.props
/* eslint-enable no-unused-vars */
const reduxFormProps = {
array,
anyTouched,
asyncValidate: this.asyncValidate,
asyncValidating,
...bindActionCreators({ blur, change }, dispatch),
clearSubmit,
destroy,
dirty,
dispatch,
error,
form,
handleSubmit: this.submit,
initialize,
initialized,
initialValues,
invalid,
pristine,
reset,
resetSection,
submitting,
submitAsSideEffect,
submitFailed,
submitSucceeded,
touch,
untouch,
valid,
warning
}
const propsToPass = {
...(propNamespace ? { [propNamespace]: reduxFormProps } : reduxFormProps),
...rest
}
if (isClassComponent(WrappedComponent)) {
;((propsToPass: any): Object).ref = this.wrapped
}
const _reduxForm = {
...this.props,
getFormState: state => getIn(this.props.getFormState(state), this.props.form),
asyncValidate: this.asyncValidate,
getValues: this.getValues,
sectionPrefix: undefined,
register: this.register,
unregister: this.unregister,
registerInnerOnSubmit: innerOnSubmit => (this.innerOnSubmit = innerOnSubmit)
}
return createElement(ReduxFormContext.Provider, {
value: _reduxForm,
children: createElement(WrappedComponent, propsToPass)
})
}
}
Form.displayName = `Form(${getDisplayName(WrappedComponent)})`
Form.WrappedComponent = WrappedComponent
Form.propTypes = {
destroyOnUnmount: PropTypes.bool,
forceUnregisterOnUnmount: PropTypes.bool,
form: PropTypes.string.isRequired,
immutableProps: PropTypes.arrayOf(PropTypes.string),
initialValues: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
getFormState: PropTypes.func,
onSubmitFail: PropTypes.func,
onSubmitSuccess: PropTypes.func,
propNamespace: PropTypes.string,
validate: PropTypes.func,
warn: PropTypes.func,
touchOnBlur: PropTypes.bool,
touchOnChange: PropTypes.bool,
triggerSubmit: PropTypes.bool,
persistentSubmitErrors: PropTypes.bool,
registeredFields: PropTypes.any
}
const connector = connect(
(state, props) => {
const {
form,
getFormState,
initialValues,
enableReinitialize,
keepDirtyOnReinitialize
} = props
const formState = getIn(getFormState(state) || empty, form) || empty
const stateInitial = getIn(formState, 'initial')
const initialized = !!stateInitial
const shouldUpdateInitialValues =
enableReinitialize && initialized && !deepEqual(initialValues, stateInitial)
const shouldResetValues = shouldUpdateInitialValues && !keepDirtyOnReinitialize
let initial = initialValues || stateInitial || empty
if (!shouldUpdateInitialValues) {
initial = stateInitial || empty
}
let values = getIn(formState, 'values') || initial
if (shouldResetValues) {
values = initial
}
const pristine = shouldResetValues || deepEqual(initial, values)
const asyncErrors = getIn(formState, 'asyncErrors')
const syncErrors = getIn(formState, 'syncErrors') || plain.empty
const syncWarnings = getIn(formState, 'syncWarnings') || plain.empty
const registeredFields = getIn(formState, 'registeredFields')
const valid = isValid(form, getFormState, false)(state)
const validExceptSubmit = isValid(form, getFormState, true)(state)
const anyTouched = !!getIn(formState, 'anyTouched')
const submitting = !!getIn(formState, 'submitting')
const submitFailed = !!getIn(formState, 'submitFailed')
const submitSucceeded = !!getIn(formState, 'submitSucceeded')
const error = getIn(formState, 'error')
const warning = getIn(formState, 'warning')
const triggerSubmit = getIn(formState, 'triggerSubmit')
return {
anyTouched,
asyncErrors,
asyncValidating: getIn(formState, 'asyncValidating') || false,
dirty: !pristine,
error,
initialized,
invalid: !valid,
pristine,
registeredFields,
submitting,
submitFailed,
submitSucceeded,
syncErrors,
syncWarnings,
triggerSubmit,
values,
valid,
validExceptSubmit,
warning
}
},
(dispatch, initialProps) => {
const bindForm = actionCreator => actionCreator.bind(null, initialProps.form)
// Bind the first parameter on `props.form`
const boundFormACs = mapValues(formActions, bindForm)
const boundArrayACs = mapValues(arrayActions, bindForm)
const boundBlur = (field, value) =>
blur(initialProps.form, field, value, !!initialProps.touchOnBlur)
const boundChange = (field, value) =>
change(
initialProps.form,
field,
value,
!!initialProps.touchOnChange,
!!initialProps.persistentSubmitErrors
)
const boundFocus = bindForm(focus)
// Wrap action creators with `dispatch`
const connectedFormACs = bindActionCreators(boundFormACs, dispatch)
const connectedArrayACs = {
insert: bindActionCreators(boundArrayACs.arrayInsert, dispatch),
move: bindActionCreators(boundArrayACs.arrayMove, dispatch),
pop: bindActionCreators(boundArrayACs.arrayPop, dispatch),
push: bindActionCreators(boundArrayACs.arrayPush, dispatch),
remove: bindActionCreators(boundArrayACs.arrayRemove, dispatch),
removeAll: bindActionCreators(boundArrayACs.arrayRemoveAll, dispatch),
shift: bindActionCreators(boundArrayACs.arrayShift, dispatch),
splice: bindActionCreators(boundArrayACs.arraySplice, dispatch),
swap: bindActionCreators(boundArrayACs.arraySwap, dispatch),
unshift: bindActionCreators(boundArrayACs.arrayUnshift, dispatch)
}
return {
...connectedFormACs,
...boundArrayACs,
blur: boundBlur,
change: boundChange,
array: connectedArrayACs,
focus: boundFocus,
dispatch
}
},
undefined,
{ forwardRef: true }
)
const ConnectedForm = hoistStatics<any, any, any, any>(connector(Form), WrappedComponent)
ConnectedForm.defaultProps = config
// build outer component to expose instance api
class ReduxForm extends React.Component<Props> {
ref: ElementRef<any> = React.createRef()
submit() {
return this.ref.current && this.ref.current.submit()
}
reset(): void {
if (this.ref) {
this.ref.current.reset()
}
}
get valid(): boolean {
return !!(this.ref.current && this.ref.current.isValid())
}
get invalid(): boolean {
return !this.valid
}
get pristine(): boolean {
return !!(this.ref.current && this.ref.current.isPristine())
}
get dirty(): boolean {
return !this.pristine
}
get values(): Values {
return this.ref.current ? this.ref.current.getValues() : empty
}
get fieldList(): string[] {
// mainly provided for testing
return this.ref.current ? this.ref.current.getFieldList() : []
}
get wrappedInstance(): ?HTMLElement {
// for testing
return this.ref.current && this.ref.current.wrapped.current
}
render() {
const { initialValues, ...rest } = this.props
return createElement(ConnectedForm, {
...rest,
ref: this.ref,
// convert initialValues if need to
initialValues: fromJS(initialValues)
})
}
}
const WithContext = hoistStatics<any, any, any, any>(
withReduxForm(ReduxForm),
WrappedComponent
)
WithContext.defaultProps = config
return WithContext
}
}
}