Search code examples
reactjsreduxmaterial-uiredux-form

ReduxForm: using formValueSelector within a FieldArray for conditional fields doesn't render immediately


This sort of works, except additional block doesn't show up when I make a service_type radio selection, it only pops up/re-renders if I complete an additional action, like adding or removing a service block or changing another field.

    import './Register.scss';
    import React, { Component, PropTypes } from 'react';
    import { connect } from 'react-redux';
    import { Field, reduxForm, FieldArray, formValueSelector } from 'redux-form';
    import MenuItem from 'material-ui/MenuItem';
    import { RadioButton } from 'material-ui/RadioButton'
    import {
      TextField,
      SelectField,
      TimePicker,
      RadioButtonGroup
    } from 'redux-form-material-ui';
    import validate from './validate';

    class OnboardPageThree extends Component {
      static propTypes = {
        handleSubmit: PropTypes.func.isRequired,
        previousPage: PropTypes.func.isRequired
      }

      constructor(props, context) {
            super(props, context);
        }

      renderGroups = ({ fields, meta: { touched, error } }) => {
        return (
          <div>
            {fields.map((service, index) =>
              <div className="service-type-group" key={index}>
                <h4>{index} Service</h4>
                <button
                  type="button"
                  className="remove-button"
                  onClick={() => fields.remove(index)}>Remove
                </button>
                <div className="field-group half">
                  <Field
                    name={`${service}.service_type`}
                    component={RadioButtonGroup}
                    floatingLabelText="Service type"
                    className="textfield">
                    <RadioButton value="first_come_first_serve" label="First-come first-served"/>
                    <RadioButton value="appointments" label="Appointment"/>
                  </Field>
                </div>
                {/* Sort of works except doesn't populate until forced re-render by adding another to array or updating a previous field */}
                {typeof this.props.services[index].service_type != undefined && this.props.services[index].service_type == "first_come_first_serve" ? <div className="fieldset">
                  <p className="label">Timeslot capacity and length when creating a first-come-first-served (line-based) service blocks</p>
                  <div className="field-group sentence-inline">
                    <Field
                      name={`${service}.max_people`}
                      type="number"
                      component={TextField}
                      label="Number of people per timeslot"
                      className="textfield"
                    />
                    <p>people are allowed every</p>
                    <Field
                      name={`${service}.time_interval`}
                      component={SelectField}
                      className="textfield">
                      <MenuItem value="5" primaryText="5 minutes" />
                      <MenuItem value="10" primaryText="10 minutes" />
                      <MenuItem value="15" primaryText="15 minutes" />
                      <MenuItem value="30" primaryText="30 minutes" />
                      <MenuItem value="60" primaryText="60 minutes" />
                      <MenuItem value="all_day" primaryText="All day" />
                    </Field>
                    <p>.</p>
                  </div>
                </div> : null}
              </div>
            )}
            <button
              type="button"
              className="action-button"
              onClick={() => fields.push({})}>Add Service
            </button>
            {touched && error && <span className="error-message">{error}</span>}
          </div>
        );
      }

      render() {
        const { handleSubmit, previousPage } = this.props;

        return (
          <form onSubmit={handleSubmit}>
            <h2>When do you provide service?</h2>
            <FieldArray name="service_options" component={this.renderGroups} />
            <div className="justify-flex-wrapper service-actions">
              <button type="button" className="back-button" onClick={previousPage}>Back</button>
              <button type="submit" className="action-button">Next</button>
            </div>
          </form>
        );
      }
    }

    OnboardPageThree = reduxForm({
      form: 'onboarding',
      destroyOnUnmount: false,
      validate
    })(OnboardPageThree)

    const selector = formValueSelector('onboarding');
    OnboardPageThree = connect(
      (state) => {
        const services = selector(state, 'service_options');
        return {
          services
        };
      }
    )(OnboardPageThree);

    export default OnboardPageThree;

Solution

  • Yes, that is true. The FieldArray does not re-render on the change of any of its items (that would be really bad for large arrays), only on size changes.

    You will have to create a separate connected component for your array items. I have worked up a working demo here.