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} /> 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} /> 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.