paint-brush
Implement Form Validation in React Without Any Librariesby@cuginoale
10,589 reads
10,589 reads

Implement Form Validation in React Without Any Libraries

by AlessioSeptember 22nd, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

The official React documentation suggests 3 possible ways to handle form submission/validation: Controlled components, Uncontrolled components and Fully-fledged solutions (3rd party libs) But none of these 3 methods are particularly appealing to me. I personally don’t like controlled components as it involves manual state management that, most of the times, leads to unneeded and inefficient re-renderings. We are left with the non trivial task of implementing the logic to validate and collect the form data. HTML5 already has the ability to validate most user data without relying on JavaScript.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Implement Form Validation in React Without Any Libraries
Alessio HackerNoon profile picture

You don’t need a fancy library when you have HTML5 and Constraint API.


The official React documentation suggests 3 possible ways to handle form submission/validation:


  • Controlled components
  • Uncontrolled components
  • Fully-fledged solutions (3rd party libs)


But none of these 3 methods are particularly appealing to me.


Controlled components:

I personally don’t like controlled components as it involves manual state management that, most of the time, leads to unneeded and inefficient re-renderings.


From the official docs:


“When using controlled components you need to write an event handler for every way your data can change and pipe all of the input state through the React component”.


Controlled components also require you to write, test, and maintain all the validation logic. Adding/removing a controlled component to a form is a time-consuming and error-prone task as it normally requires touching all the form validation and submission logic.


Uncontrolled components:

React docs suggest implementing uncontrolled components using a ref to get form values from the DOM but then don’t provide much info on what the best practices are to extract the data and validate it.


Either way, we are left with the nontrivial task of implementing the logic to validate and collect the form data.


These days there are plenty of 3rd party libraries that do exactly that... but do we really need one?


The goal

Let’s build a simple form like this one:


Final result:

An interactive demo is available here.


We want to build a form implementing the following requirements:

  • Name and Email are mandatory

  • Email should represent a valid email address

  • The address is optional, no constraints

  • Tel is optional but if entered it should represent a formally correct UK phone number


On submission the form gets validated and should show validation errors like this:


In case of validation errors, the focus should be moved to the first invalid field.


HTML5 and Constraint API

(Source MDN): HTML5 already has the ability to validate most user data without relying on JavaScript. This is done by using validation attributes on form elements.


  • required: Specifies whether a form field needs to be filled in before the form can be submitted.

  • minlength and maxlength: Specifies the minimum and maximum length of textual data (strings).

  • min and max: Specifies the minimum and maximum values of numerical input types.

  • type: Specifies whether the data needs to be a number, an email address, or some other specific preset type.

  • pattern: Specifies a regular expression that defines a pattern the entered data needs to follow.


If the data entered in a form field follows all of the rules specified by the above attributes, it is considered valid. If not, it is considered invalid.


Most browsers support the Constraint Validation API, which consists of a set of methods and properties that enable checking values that users have entered into form controls, before submitting the values to the server.


With these tools at our disposal, we can build custom components for easy and efficient form management.


The solution

The building blocks of our solution are the following two custom components:

  • <Form />
  • <TextInput />


<Form />

The key attribute here is noValidate.


The novalidate attribute turns off the browser's automatic validation error messages. Without that our form would look like this:


default validation feedback


All the magic happens in the handleSubmit callback:


  • we stop the form submission with e.preventDefault()

  • we make a note of the form validity state with isValid

  • we add the “submitted” className to improve the UX (more on this in a moment)

  • we move the focus to the first invalid field if any

  • we collect the form data and call the onSubmit callback, if valid


The last one is another key point of this solution: collecting the form data using the FormData object. There is no need to manually go through the form elements and extract their values.


“The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values” —( source: MDN)



<TextInput />

The TextInput component is responsible for rendering the input field, the label, and the possible validation error.


You can see the

, but essentially this component sets an internal state variable with the validation error string coming from the constraint API and resets it on blur if the error has been fixed:


Implementation

The code to implement the form in the example looks like this:


Things to notice:

  • We set the required attribute on Name and Email
  • type=”email” makes sure Constraint API checks the input is a valid email address
  • Address as no validation rules
  • Tel is not required but we set the pattern attribute to check the input against the regex (setting type=tel doesn’t enforce any built-in validation)


Whenever we need to add another field to the form we just add a new TextInput and we set the relevant attributes to it.


No need to update schemas or add yet another useState to track the value of the new field, let’s just offload the heavy lifting to the built-in API!


Improving the UX

The final touch to improve the UX is to show validation errors only after the form gets submitted.

Using just the :invalid pseudo-class in our CSS to style incorrect fields would cause red highlighted input boxes and error messages to appear as soon as the page loads… and we don’t want to scream in the face of a user that his/her input is incorrect before they even have the chance to type a character in!


For this reason, we add the .submitted className to the form and style the TextInput with this:



i18n

One great thing to keep in mind is that the constraint API validation messages are localized by default — i.e. they come in the locale the user OS is set to!


If you are happy with the default messages then there is nothing else you need to worry about: your English, Spanish, Italian or Chinese users will get the messages in their own language.


The TextInput component provided in the example allows also for basic validation message customization via the errorText attribute:



It is just an example to show how one could customize those messages.



Finally

Once the form is formally correct we can actually submit the form in the onSubmit callback.

We receive a FormData object which can then be easily sent using the fetch() or XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data" , or we can transform it into a more familiar key/value object:


to produce the following:


Final words

There is beauty in simplicity and form validation/submission doesn’t get much simpler than this IMHO 🙂


Any comment/feedback is much appreciated!


Link to part two: a not-so-trivial example.



Also published here.