As a web developer who is relatively new to ReactJS, I take for granted the built-in support for form validation that I’ve enjoyed in other JavaScript frameworks, like Angular and Sencha. What follows is an elaboration on this problem, as well as a discussion of two approaches to solving it in React, on which I experimented for the same basic form design.
So what do I mean by “support for form validation”? Angular 1, for example, affords us a form model with properties, like $valid
, $invalid
, $pristine
, and $dirty
, out of the box. These occur at two levels: at the level of form inputs and at the level of the entire form that contains them. They facilitate validation logic at both levels and displaying feedback to users. Although the framework leaves many UI decisions to designers and developers, such as how and where to use these properties, it also provides us with specific input-type validators, like date and email. Sencha’s classic ExtJS toolkit affords us even more support than Angular 1. Not only does it provide input-type validators, but also display themes that we can customize or replace with another design theme.
React alone is relatively bare-bones when it comes to supporting form validation. Of course, we can always fall back on whatever HTML5 “constraint validation” support the browser provides. For example, using the type
, required
, and pattern
attributes on input[type="text"]
elements and the :valid
and :invalid
CSS pseudo-classes. But we may very well want more control over validation than browser API’s alone afford us. This is especially so in single-page applications, where we don’t normally submit forms in the standard way (that is, the way to trigger localized error messages in the browser) and in legacy browsers. Typically, we add novalidate
attribute to the form, cancel submit events via e.preventDefault()
, and rely on JS to extend native browser capability. (See here for more on browser support and its limitations.)
Whereas the aforementioned frameworks come fully baked with this extension, whether we want it or not, React leaves us with the 3 options:
- Rely on browser API’s
- Code a JS solution from scratch
- Install another JS library – ideally one that plays nicely with React
I decided to experiment with two of these approaches when developing the same form component in React, both of which are available in public code pens (see links below) and appear like so:
The first approach relies on the browser’s constraint validation API and actually provides a lot of control via the ValidityState object and setCustomValidity method, which occur in modern browsers on each input element. The custom showInputError and showFormErrors functions require more coding than we might be accustomed to, but the standard API has already taken care of most of the validation logic for us, so we’re simply checking that the passwords match and deciding what error messages to show based on the standard API.
if (isPasswordConfirm) { if (this.refs.password.value !== this.refs.passwordConfirm.value) { this.refs.passwordConfirm.setCustomValidity('Passwords do not match'); } else { this.refs.passwordConfirm.setCustomValidity(''); } } if (!validity.valid) { if (validity.valueMissing) { error.textContent = `${label} is a required field`; } else if (validity.typeMismatch) { error.textContent = `${label} should be a valid email address`; } else if (isPassword && validity.patternMismatch) { error.textContent = `${label} should be longer than 4 chars`; } else if (isPasswordConfirm && validity.customError) { error.textContent = 'Passwords do not match'; } return false; }
The second approach relies on react-redux-form, a third-party library that integrates form data from React components into a Redux store. This approach has a steeper learning curve than the previous one and relies less on web standards (in the W3C sense) than on conventional practices within the React community. That said, it bears some similarities to the Angular form model, such as pristine
, touched
, and valid
properties on a $form
object and on each field object. It also allows us to set validators and error messages within JSX markup, which is reminiscent of Angular directives.
<div className="form-group"> <label>Username</label> <Control.text model=".username" component={MyTextInput} validators={{ required: val => val && val.length, validEmail: validator.isEmail }} /> <Errors className="errors" model="user.username" show={showErrors} messages={{ required: 'Username is required', validEmail: 'Username should be a valid email address', }} /> </div>
If one is already comfortable with Redux concepts, like actions and reducers, there is only the matter of getting comfortable with the specific react-redux-form API. Admittedly, this was not as easy to use as I had hoped, based on its clean and elegant documentation. Once I properly connected my UserForm component to my Redux store, I was able to listen for changes to data state (e.g. on the rrf/setSubmitFailed
action) in my lower-level components (e.g. MyTextInput
) and thereby achieve more control over changes in the view state. As a bonus, I added the redux-logger, so that I’m able to view all changes to data state in the browser console, including the form model that’s now part of my Redux store.
Both of these approaches are completely valid (no pun intended!) and each has its own strengths and weaknesses. In summary, the first approach benefits from its simple reliance on the “constraint validation” API, which is already a standard in modern browsers. Web standards like these have much slower churn rates, relative to trendy frameworks and libraries, which can translate into reduced fatigue for us developers. While that API was sufficient for the purposes of this demo, it may prove to be limited for some projects, such as those that require support for legacy browsers. Meanwhile, the second approach benefits from having leveraged the powers of Redux and Angular-like properties, but also probably isn’t worth the trouble of setting up on smaller projects, where Redux alone would seem like overkill. Even if an application is already configured for Redux, a form model arguably belongs where forms live – in a view component – and not in a data store at the level of the entire application.
Jason Malfatto
Related Posts
-
Ext JS to React: Form Submission
This is part of the Ext JS to React blog series. You can review the…
-
Ext JS to React: Form Validations
This is part of the Ext JS to React blog series. You can review the…