erikras/redux-form

View on GitHub
src/createFormValues.js

Summary

Maintainability
A
0 mins
Test Coverage
// @flow
import { isEmpty, isEqual, mapValues } from 'lodash'
import React from 'react'
import { connect } from 'react-redux'
import prefixName from './util/prefixName'
import { withReduxForm } from './ReduxFormContext'
import type { ComponentType } from 'react'
import type { Structure } from './types'
import type { FormValuesInterface } from './formValues.types'

export default function createValues({ getIn }: Structure<any, any>): FormValuesInterface {
  return (firstArg: string | Object | Function, ...rest: string[]) => {
    // create a class that reads current form name and creates a selector
    // return
    return (Component: ComponentType<any>): ComponentType<any> => {
      class FormValues extends React.Component<Object> {
        Component: ComponentType<any>
        _valuesMap: Object

        constructor(props: Object) {
          super(props)
          if (!props._reduxForm) {
            throw new Error(
              'formValues() must be used inside a React tree decorated with reduxForm()'
            )
          }
          this.updateComponent(props)
        }

        UNSAFE_componentWillReceiveProps(props) {
          if (typeof firstArg === 'function') {
            this.updateComponent(props)
          }
        }

        render() {
          const { Component } = this
          return (
            <Component
              // so that the connected component updates props when sectionPrefix has changed
              sectionPrefix={this.props._reduxForm.sectionPrefix}
              {...this.props}
            />
          )
        }

        updateComponent(props) {
          let valuesMap: Object
          const resolvedFirstArg: string | Object =
            typeof firstArg === 'function' ? firstArg(props) : firstArg
          if (typeof resolvedFirstArg === 'string') {
            valuesMap = rest.reduce(
              (result, k) => {
                result[k] = k
                return result
              },
              { [resolvedFirstArg]: resolvedFirstArg }
            )
          } else {
            valuesMap = resolvedFirstArg
          }
          if (isEmpty(valuesMap)) {
            // maybe that empty valuesMap is ok if firstArg is a function?
            // if this is the case, we probably should set this.Component = Component
            throw new Error(
              'formValues(): You must specify values to get as formValues(name1, name2, ...) or formValues({propName1: propPath1, ...}) or formValues((props) => name) or formValues((props) => ({propName1: propPath1, ...}))'
            )
          }
          if (isEqual(valuesMap, this._valuesMap)) {
            // no change in valuesMap
            return
          }
          this._valuesMap = valuesMap
          this.setComponent()
        }

        setComponent() {
          const formValuesSelector = (_, { sectionPrefix }) => {
            // Yes, we're only using connect() for listening to updates.
            // The second argument needs to be there so that connect calls
            // the selector when props change
            const { getValues } = this.props._reduxForm
            const values = getValues()
            return mapValues(this._valuesMap, (path: string) =>
              getIn(values, prefixName(this.props, path))
            )
          }
          this.Component = connect(
            formValuesSelector,
            () => ({}) // ignore dispatch
          )(({ sectionPrefix, ...otherProps }) => <Component {...otherProps} />)
        }
      }

      return withReduxForm(FormValues)
    }
  }
}