Redux Form render multiple fieldArray dynamically on the same page, with validation

Redux Form comes with an option to use fieldArray and render a group of fields dynamically. But what if we want to render multiple fieldArray on the same page?

For example: In a form, we want to show 7 week days, and each week day can have as many events as user wants. We can implement it as follows:

Main Form

import React from 'react';
import { Field, FieldArray, reduxForm } from 'redux-form';

const DAYS = [
  {
    id: 0,
    name: "Sun",
  },
  {
    id: 1,
    name: "Mon",
  },
  {
    id: 2,
    name: "Tue",
  },
  {
    id: 3,
    name: "Wed",
  },
  {
    id: 4,
    name: "Thu",
  },
  {
    id: 5,
    name: "Fri",
  },
  {
    id: 6,
    name: "Sat",
  },
];

class DaysForm extends React.Component {
  submit = (values) => {
    console.log(values);
  }
  render() {
    return <form onSubmit={this.props.handleSubmit(this.submit)}>
      {
        DAYS.map(day => {
          return <div>
            {day.name}
            <FieldArray name={`days.${day.id}`} component={renderEvents} />
            <br />
          </div>
        })
      }
      <button type="submit">Submit</button>
    </form>
  }
}

DaysForm = reduxForm({
  form: 'daysForm',
  validate
})(DaysForm)

Import react and modules required for redux-form, including FieldArray. Initialize the component and pass it to reduxForm with form name as ‘daysForm’.

DAYS is a constant array to render 7 week days with a loop. Inside the component, we call FieldArray for each week day and pass a unique name to it. Note that if no unique name is passed, fields will duplicate across all week days.

renderEvents and renderFields

const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <div>
    <input {...input} type={type} placeholder={label} /> &nbsp;2
    {touched && error && <span style={{ color: "red", fontSize: "12px" }}>{error}</span>}
  </div>
)

const renderEvents = ({ fields, meta: { error, submitFailed } }) => (
  <div>
    {fields.map((member, index) => (
      <div key={index}>
        <h4>Event {index + 1}</h4>
        <Field
          name={`${member}.eventName`}
          type="text"
          component={renderField}
          label="Event Name"
        />
        <Field
          name={`${member}.noOfPeople`}
          type="number"
          component={renderField}
          label="Expected People"
        />
        <button
          type="button"
          title="Remove Event"
          onClick={() => fields.remove(index)}
        >x</button>
      </div>
    ))}

    <div>
      <button type="button" onClick={() => fields.push({})}>
        +
      </button>
      {submitFailed && error && <span>{error}</span>}
    </div>
  </div>
)

renderEvents has the usual code that goes with FieldArray implementation. It loops through the fields and in each case show add, remove buttons, and two input fields: eventName and noOfPeople.

Validation

const validate = values => {
  const errors = {
    days: [
      [],
      [],
      [],
      [],
      [],
      [],
      [],
    ]
  }

  if (values.days) {
    DAYS.forEach((day) => {
      if (values.days[day.id]) {
        values.days[day.id].forEach((event, eventIndex) => {
          const err = {};
          if (!event.eventName) {
            err.eventName = "Required"
          }
          errors.days[day.id][eventIndex] = err;
        })
      }
    });
  }

  return errors;
}

For validation we simply need to check each day that it has an event object or not. If there’s an event object (that is, + has been clicked), but the eventName field has not been clicked, add “Required” error to that event.

Complete Code

import React from 'react';
import { Field, FieldArray, reduxForm } from 'redux-form';

const DAYS = [
  {
    id: 0,
    name: "Sun",
  },
  {
    id: 1,
    name: "Mon",
  },
  {
    id: 2,
    name: "Tue",
  },
  {
    id: 3,
    name: "Wed",
  },
  {
    id: 4,
    name: "Thu",
  },
  {
    id: 5,
    name: "Fri",
  },
  {
    id: 6,
    name: "Sat",
  },
];

const validate = values => {
  const errors = {
    days: [
      [],
      [],
      [],
      [],
      [],
      [],
      [],
    ]
  }

  if (values.days) {
    DAYS.forEach((day) => {
      if (values.days[day.id]) {
        values.days[day.id].forEach((event, eventIndex) => {
          const err = {};
          if (!event.eventName) {
            err.eventName = "Required"
          }
          errors.days[day.id][eventIndex] = err;
        })
      }
    });
  }

  return errors;
}

const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <div>
    <input {...input} type={type} placeholder={label} /> &nbsp;2
    {touched && error && <span style={{ color: "red", fontSize: "12px" }}>{error}</span>}
  </div>
)

const renderEvents = ({ fields, meta: { error, submitFailed } }) => (
  <div>

    {fields.map((member, index) => (
      <div key={index}>
        <h4>Event {index + 1}</h4>
        <Field
          name={`${member}.eventName`}
          type="text"
          component={renderField}
          label="Event Name"
        />
        <Field
          name={`${member}.noOfPeople`}
          type="number"
          component={renderField}
          label="Expected People"
        />
        <button
          type="button"
          title="Remove Event"
          onClick={() => fields.remove(index)}
        >x</button>
      </div>
    ))}

    <div>
      <button type="button" onClick={() => fields.push({})}>
        +
      </button>
      {submitFailed && error && <span>{error}</span>}
    </div>
  </div>
)


class DaysForm extends React.Component {
  submit = (values) => {
    // Form submission comes here if there's no validation error
    console.log(values);
  }
  render() {
    return <form onSubmit={this.props.handleSubmit(this.submit)}>
      {
        DAYS.map(day => {
          return <div>
            {day.name}
            <FieldArray name={`days.${day.id}`} component={renderEvents} />
            <br />
          </div>
        })
      }
      <button type="submit">Submit</button>
    </form>
  }
}

DaysForm = reduxForm({
  form: 'daysForm',
  validate
})(DaysForm)

export default DaysForm


Submitted Form Values

The submitted form values with events on Sunday, Wednesday, and Saturday would look something like this:

{
  "days": [
    [
      {
        "eventName": "Smith's Birthday",
        "noOfPeople": "20"
      },
      {
        "eventName": "Sasha's Wedding",
        "noOfPeople": "100"
      }
    ],
    null,
    null,
    [
      {
        "eventName": "College Farewell",
        "noOfPeople": "150"
      }
    ],
    null,
    null,
    [
      {
        "eventName": "John's Birthday",
        "noOfPeople": "22"
      },
      {
        "eventName": "Official Dinner",
        "noOfPeople": "30"
      }
    ]
  ]
}

Note that the days with no event have null in them.