Skip to content

Handling a change to Redux-Form data from container to component via props

Published:

I ended up realizing that I didn’t need to use context for this after all and used this pattern as an alternative.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { FieldArray, getFormValues } from 'redux-form' // import the action creator you need
import CountryPicker from '../components/CountryPicker'

class CountriesContainer extends Component {

  constructor(props) {
    super(props)
    this.state = { countries: props.formValues.countries }
  }

  static propTypes = {
    formValues: PropTypes.object
  }

  componentWillReceiveProps = (nextProps) => {
    if (nextProps.formValues && nextProps.formValues.countries !== this.props.formValues.countries) {
      this.setState({ countries: nextProps.formValues.countries })
    }
  }

  shouldComponentUpdate = (nextProps) => {
    if (nextProps.formValues) return (nextProps.formValues.countries !== this.props.formValues.countries)
  }

  handleCountryChange = () => this.setState({ countries: this.props.formValues.countries })

  render() {
    const { countries } = this.state
    const options = ['United States', 'Canada', 'Mexico']

    return (
      <div>
        <FieldArray component={CountryPicker}
          name='countries'
          options={options}
          selectedCountries={countries}
          onCountryChange={this.handleCountryChange}
        />
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  const { countries } = state
  return {
    countries,
    formValues: getFormValues('countriesForm')(state)
  }
}
export default connect(
  mapStateToProps
)(CountriesContainer)
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { List, Checkbox, Table } from 'semantic-ui-react'
import indexOf from 'lodash/indexOf'

class CountryPicker extends Component {
  constructor(props) {
    super(props)
    this.state = {
      options: props.options,
      selected: props.selectedCountries,
      fields: props.fields
    }
    this.handleCountryChange = this.handleCountryChange.bind(this)
  }

  static propTypes = {
    options: PropTypes.array.isRequired,
    selectedCountries: PropTypes.array.isRequired,
    fields: PropTypes.any,
    onCountryChange: PropTypes.func.isRequired
  }

  shouldComponentUpdate(nextProps) {
    if (nextProps.selectedCountries !== this.props.selectedCountries) {
      this.handleCountryChange()
      return true
    }
    return false
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      selected: nextProps.selectedCountries
    })
  }

  handleCountryChange() {
    this.props.onCountryChange(this.state.regionKey)
  }

  handleSelectAll = () => {
    const { options, selected, fields } = this.state
    let fieldsToAdd, fieldsToRemove
    if (selected.length === options.length) {
      // deselect all
      fieldsToRemove = selected.map((country) => indexOf(selected, country))

      // Making sure the removal of indices won't be affected by race conditions
      // by sorting them in descending order
      fieldsToRemove.sort((a, b) => b - a)
      fieldsToRemove.forEach((countryIndex) => fields.remove(countryIndex))
    } else {
      fieldsToAdd = options.reduce((result, country) => {
        if (indexOf(selected, country) === -1) result.push(country)
        return result
      }, [])
      fieldsToAdd.forEach((country) => fields.push(country))
    }
  }

  handleChange = (e, { value }) => {
    const { selected, fields } = this.state
    if (indexOf(selected, value) !== -1) {
      fields.remove(indexOf(selected, value))
    } else {
      fields.push(value)
    }
  }

  render() {
    const { options, selected } = this.state
    return (
      <Table compact>
        <Table.Header>
          <Table.Row>
            <Table.HeaderCell>
              <Checkbox checked={options.length > 0 && selected.length === options.length} label='Countries'
                indeterminate={0 !== selected.length && selected.length < options.length} onChange={this.handleSelectAll} onClick={this.handleSelectAll}
                disabled={options.length === 0} />
            </Table.HeaderCell>
          </Table.Row>
        </Table.Header>
        <Table.Body>
          <Table.Row>
            <Table.Cell>
              <List verticalAlign='middle' relaxed style={{ height: '180px', overflowY: 'auto' }}>
                {options.map((country, i) =>
                  <List.Item key={i}>
                    <List.Content>
                      <Checkbox label={country} name='countries' value={country} checked={indexOf(selected, country) !== -1} onChange={this.handleChange} />
                    </List.Content>
                  </List.Item>
                )}
              </List>
            </Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table>
    )
  }
}

export default CountryPicker