I have a Redux Form project where I’m using redux-form-validators
to get easy Rails-style validations. Its docs explain how to define validators for normal scalar fields, but I thought I’d add how to do the same for a FieldArray
, which is a list of zero of more sub-fields.
In my case I’m working on a time-tracking and invoicing application, where admins can edit a client. The form has regular fields for the client’s name, the invoicing frequency, etc., and then it also has “work categories”. Each client has one or more work categories, and a work category is just a name and an hourly rate. For instance you might charge one rate for design and another for development, or you might track retainer hours at $0/hour and extra hours at your normal rate.
Redux Form makes it really easy to include child records right inside the main form using FieldArray
. Their docs give a nice example of how to validate those nested fields, but it’s pretty DIY and verbose.
With redux-form-validators
on the other hand, it’s easy. First you define a validation object with the rules for each field, like so:
const validations = {
name: [required()],
paymentDue: [required(), numericality({int: true, '>=': 0})],
// ...
};
Then you write a little validation function to pass to Redux Form:
const validate = function(values) => {
const errors = {}
for (let field in validations) {
let value = values[field]
errors[field] = validations[field].map(validateField => {
return validateField(value, values)
}).find(x => x) // Take the first non-null error message.
}
return errors
};
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({
form: 'editClient',
validate
})(EditClientPage)
);
That is just straight from the docs. For each field it iterates over its validators and reports the first error it finds. It’s simple, but it doesn’t know how to handle nesting like with FieldArray
.
But notice you can really do whatever you like. It’s a bit striking for someone used to Rails how exposed and customizable everything is here. So how can we rewrite validate
to be smarter?
Ideally we’d like to support validations both on individual sub-fields, like the name
of a single work category, and also on the workCategories
as a whole, for example checking that you have at least one. This is exactly what the Redux Form example does above: it checks that your club has at least one member, and that each member has a first and last name.
Because this is a FieldArray
, Redux Form expects a different structure for its entry in the errors object returned by validate
. Normally you’d have a string value, like this:
{
paymentDue: 'must be numeric'
}
But for a FieldArray
you want to pass an array with one error object for each corresponding FieldArray
element, like this:
{
workCategories: [
{}, // no errors
{name: 'is required'},
{name: 'is too short', rate: 'must be numeric'},
]
}
Those sub-errors will get passed in the meta
object given to the component used to render each Field
. Again, you can see that happening in the Redux Form example.
In addition, the array may have its own _error
attribute for top-level errors. That gets passed as meta.error
to the FieldArray
component itself. So _error
is not just a convention; it’s a “magic” attribute built into Redux Form. We want to set it too.
I want a way to define all these validations in one big object: top-level fields, the FieldArray
itself, and the fields of individual FieldArray
records. Here is the structure I set up:
const validations = {
name: [required()],
paymentDue: [required(), numericality({int: true, '>=': 0})],
...
workCategories: {
_error: [
required({msg: "Please enter at least one work category."}),
length({min: 1, msg: "Please enter at least one work category."})
],
name: [required()],
rate: [required(), numericality({'>=': 0})],
},
};
Then instead of the recommended validate
function I used this:
const buildErrors = (validations) => (values) => {
const errors = {};
for (let field in validations) {
if (field === '_error') continue;
let value = values[field];
const fieldValidations = validations[field];
if (fieldValidations.constructor === Array) {
errors[field] = fieldValidations
.map(validateField => validateField(value, values))
.find(x => x);
} else {
errors[field] = value ? value.map(o => buildErrors(fieldValidations)(o)) : [];
if (fieldValidations._error) {
errors[field]._error = fieldValidations._error
.map(validateField => validateField(value, values))
.find(x => x);
}
}
return errors;
}
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({
form: 'editClient',
validate: buildErrors(validations),
})(EditClientPage)
);
There are a few things to note here: I’m using a recursive function, where each call handles one “level” of the validations
object. It iterates over the field names, and if the field has an array, it handles it as before. Otherwise it expects a nested object structured just like the outermost object, which each sub-field has its own array of validators. There may also be a list of validators under _errors
, and those are handled specially. I’m using currying so that I can build a top-level validation function for reduxForm
as well as nested functions for each FieldArray
.
This function also supports reporting errors both on sub-fields and the whole FieldArray
at the same time. That wouldn’t happen if the only FieldArray
check was to have at least one element, like here, but you can imagine scenarios where you want to validate the FieldArray
as a whole even when it isn’t empty.
I’m happy that this approach lets me combine the ease of redux-form-validators
with FieldArray
to get the best of both worlds. I also like that buildErrors
is general enough that I can move it to a utility collection, and not write it separately for each form in my app.
Also: you might enjoy my follow-up article showing how to render the form Field
s and FieldArray
s with Pug and Bootstrap.