If you have made a React form with any kind of validation, you may think to yourself:

Well, it kind of feels boilerplate’y to validate all the fields in the form component and then pass down the error messages. Wouldn’t it be cool to have something like <Input ... isRequired /> and have it show the red border instead?

It’s so tempting to “cut the boilerplate”!

The boilerplate is bad, right?

As it stands, though, there are several… issues with doing something like this. Let’s see.

1. Validation status isn’t “local” to fields

We could keep the validation state to each individual input component. But then the form component won’t know whether a particular field is valid or not.

Bummer.

It means:

you won’t be able to disable the submit button if some fields are invalid

conditionally show some other field depending on the validation status of this one and so on.

One immediate idea might be to pass in onValidationStatusChanged to each input… but then you’ve come a whole circle and are back to the boilerplate. Not only that, there would be other issues with that. Read on.

A more thought-out idea might be to implement a clever field that would “automagically” put its validation status into the form component’s state. This is, in fact, what React Reform does. That is a bad idea on several counts:

implicit APIs are harder to understand

the data flow is reversed

2. Not all validations are field-centric

It’s true that some validations operate in the context of a single field. That is not always the case, however.

Going for the field-centric approach, you now have to introduce a different API, pretty much like the boilerplate that you’re looking to get rid of, just to support this case.

So you’re not actually getting rid of the boilerplate. But you now have two different APIs for what’s essentially the same fucking thing.

3. Data flow is reversed

In Re act, the view is traditionally a representation of some data, some state.

When you see a list of todos in a todo app, it’s just a rendering of the array of todo objects.

In much the same way, a form component is where Things Happen™ in React forms. A form owns its data — aka values of its inputs. It’s only natural if the form also expresses its data validation rules somewhere inside.

A form component is a unit, you could say.

However, when validation rules become buried in render , this data relationship is effectively removed. You now have to carefully scan the render method to understand how’s the form, as a whole, validated.

Two examples where this is Done Right™ are:

self-rolled validations from the instant field validation post: function validate ( email , password ) { // true means invalid, so our conditions got reversed return { email : email . length === 0 , password : password . length === 0 , }; }

Redux-form, which makes this data relationship first-class, by accepting a validation function in the form decorator: const validate = values => { const errors = {}; if ( ! values . email ) { errors . email = 'Required' ; } if ( ! values . password ) { errors . password = 'Required' ; } return errors ; } export default reduxForm ({ form : 'signInForm' , validate , })( SignInForm )

(The list is not exhaustive, of course!)

4. All fields have to be on-screen

While often, you’d want to validate everything that’s visible on the screen, there could be cases for when a field is not displayed — but should be validated nevertheless.

Multi-step forms are one such example.