repo_description: A Higher Order Component using react-redux to keep form state in a Redux store
repo_topics: form form-validation forms react redux redux-form validation
code: // @flow
  import React, { Component, createElement } from 'react'
  import PropTypes from 'prop-types'
  import { connect } from 'react-redux'
  import { bindActionCreators } from 'redux'
  import createFieldArrayProps from './createFieldArrayProps'
  import { mapValues } from 'lodash'
  import plain from './structure/plain'
  import type { ElementRef } from 'react'
  import type { Structure } from './types'
  import type { Props, DefaultProps } from './ConnectedFieldArray.types'
  import validateComponentProp from './util/validateComponentProp'
  
  const propsToNotUpdateFor = ['_reduxForm', 'value']
  
  export default function createConnectedFieldArray(structure: Structure<any, any>) {
    const { deepEqual, getIn, size, equals, orderChanged } = structure
    const getSyncError = (syncErrors: Object, name: string) => {
      // For an array, the error can _ONLY_ be under _error.
      // This is why this getSyncError is not the same as the
      // one in Field.
      return plain.getIn(syncErrors, `${name}._error`)
    }
  
    const getSyncWarning = (syncWarnings: Object, name: string) => {
      // For an array, the warning can _ONLY_ be under _warning.
      // This is why this getSyncError is not the same as the
      // one in Field.
      return getIn(syncWarnings, `${name}._warning`)
    }
  
    class ConnectedFieldArray extends Component<Props> {
      static defaultProps: DefaultProps
      ref: ElementRef<any> = React.createRef()
  
      shouldComponentUpdate(nextProps: Props) {
        // Update if the elements of the value array was updated.
        const thisValue: any = this.props.value
        const nextValue: any = nextProps.value
  
        if (thisValue && nextValue) {
          const nextValueItemsSame = equals(nextValue, thisValue) //.every(val => ~thisValue.indexOf(val))
          const nextValueItemsOrderChanged = orderChanged(thisValue, nextValue)
          const thisValueLength = thisValue.length || thisValue.size
          const nextValueLength = nextValue.length || nextValue.size
  
          if (
            thisValueLength !== nextValueLength ||
            (nextValueItemsSame && nextValueItemsOrderChanged) ||
            (nextProps.rerenderOnEveryChange &&
              thisValue.some((val, index) => !deepEqual(val, nextValue[index])))
          ) {
            return true
          }
        }
  
        const nextPropsKeys = Object.keys(nextProps)
        const thisPropsKeys = Object.keys(this.props)
        // if we have children, we MUST update in React 16
        // https://twitter.com/erikras/status/915866544558788608
        return !!(
          this.props.children ||
          nextProps.children ||
          nextPropsKeys.length !== thisPropsKeys.length ||
          nextPropsKeys.some(prop => {
            // useful to debug rerenders
            // if (!plain.deepEqual(this.props[ prop ], nextProps[ prop ])) {
            //   console.info(prop, 'changed', this.props[ prop ], '==>', nextProps[ prop ])
            // }
            return (
              !~propsToNotUpdateFor.indexOf(prop) && !deepEqual(this.props[prop], nextProps[prop])
            )
          })
        )
      }
  
      get dirty(): boolean {
        return this.props.dirty
      }
  
      get pristine(): boolean {
        return this.props.pristine
      }
  
      get value(): any {
        return this.props.value
      }
  
      getRenderedComponent() {
        return this.ref.current
      }
  
      getValue = (index: number): any => this.props.value && getIn(this.props.value, String(index))
  
      render() {
        const {
          component,
          forwardRef,
          name,
          _reduxForm, // eslint-disable-line no-unused-vars
          validate, // eslint-disable-line no-unused-vars
          warn, // eslint-disable-line no-unused-vars
          rerenderOnEveryChange, // eslint-disable-line no-unused-vars
          ...rest
        } = this.props
        const props = createFieldArrayProps(
          structure,
          name,
          _reduxForm.form,
          _reduxForm.sectionPrefix,
          this.getValue,
          rest
        )
        if (forwardRef) {
          props.ref = this.ref
        }
        return createElement(component, props)
      }
    }
  
    ConnectedFieldArray.propTypes = {
      component: validateComponentProp,
      props: PropTypes.object,
      rerenderOnEveryChange: PropTypes.bool
    }
  
    ConnectedFieldArray.defaultProps = {
      rerenderOnEveryChange: false
    }
  
    const connector = connect(
      (state, ownProps) => {
        const {
          name,
          _reduxForm: { initialValues, getFormState }
        } = ownProps
        const formState = getFormState(state)
        const initial =
          getIn(formState, `initial.${name}`) || (initialValues && getIn(initialValues, name))
        const value = getIn(formState, `values.${name}`)
        const submitting = getIn(formState, 'submitting')
        const syncError = getSyncError(getIn(formState, 'syncErrors'), name)
        const syncWarning = getSyncWarning(getIn(formState, 'syncWarnings'), name)
        const pristine = deepEqual(value, initial)
        return {
          asyncError: getIn(formState, `asyncErrors.${name}._error`),
          dirty: !pristine,
          pristine,
          state: getIn(formState, `fields.${name}`),
          submitError: getIn(formState, `submitErrors.${name}._error`),
          submitFailed: getIn(formState, 'submitFailed'),
          submitting,
          syncError,
          syncWarning,
          value,
          length: size(value)
        }
      },
      (dispatch, ownProps) => {
        const { name, _reduxForm } = ownProps
        const {
          arrayInsert,
          arrayMove,
          arrayPop,
          arrayPush,
          arrayRemove,
          arrayRemoveAll,
          arrayShift,
          arraySplice,
          arraySwap,
          arrayUnshift
        } = _reduxForm
        return mapValues(
          {
            arrayInsert,
            arrayMove,
            arrayPop,
            arrayPush,
            arrayRemove,
            arrayRemoveAll,
            arrayShift,
            arraySplice,
            arraySwap,
            arrayUnshift
          },
          actionCreator => bindActionCreators(actionCreator.bind(null, name), dispatch)
        )
      },
      undefined,
      { forwardRef: true }
    )
    return connector(ConnectedFieldArray)
  }
  