Form validation/submission in vanilla React (no libraries)
Tags: react, html, form, validation
Published: 2022-08-23 16:00:18

---
title: Form validation/submission in vanilla React (no libraries)
published: true
description: You don’t need a fancy library when you have HTML5 and Constraint API.
tags: react, html5, form, validation
cover_image: https://miro.medium.com/max/1400/1*WQzdY4d3LAK_NyV-ym3big.png
# Use a ratio of 100:42 for best results.
---

# Form validation/submission in vanilla React (no libraries)

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

_Link to part-two: [a not so trivial example](https://dev.to/cuginoale/form-validationsubmission-in-vanilla-react-part-2-3gok)_
***

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 times, leads to unneeded and inefficient re-renderings.

**Uncontrolled components**: React docs suggests implementing uncontrolled components using a  [ref](https://reactjs.org/docs/refs-and-the-dom.html)  to get form values from the DOM but then doesn’t provide much info on what the best practises are to extract the data and validate it.

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

These days there are  [plenty of  **3rd party libraries**](https://blog.logrocket.com/react-form-validation-sollutions-ultimate-roundup/)  that do exactly that... but do we really need one?

***

# The goal

Let’s build a simple form like this one:

![](https://miro.medium.com/max/355/1*83CPpXFjyhuL96r-Rtt9dg.png)



_Interactive_ [_demo available here_](https://ghxg1m.csb.app/)_._

We want to build a form implementing the following requirements:

-   **Name**  and  **Email**  are mandatory
-   **Email**  should represent a valid email address
-   **Address**  is optional, no constraints
-   **Tel**  is optional but if entered it should represent a formally correct UK phone number

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

![](https://miro.medium.com/max/355/1*79jb5OUbFtXM8gYoxb38kg.png)

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

***

# HTML5 and Constraint API

_(Source_ [_MDN_](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation)_)_: HTML5 already has the ability to validate most user data without relying on JavaScript. This is done by using  [validation attributes on form elements](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#using_built-in_form_validation).

-   [required](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required): Specifies whether a form field needs to be filled in before the form can be submitted.
-   [minlength](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/minlength)  and  [maxlength](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength): Specifies the minimum and maximum length of textual data (strings).
-   [min](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min)  and  [max](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/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](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern): Specifies a  [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)  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](https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation), 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 />`

![](https://miro.medium.com/max/528/1*M-W6FMCbeE151xwVEJF5cw.png)

The key attribute here is  **noValidate.**

The  [novalidate](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/novalidate)  attribute turns off the browser's automatic validation error messages. Without that our form would look like this:

![default validation feedback](https://miro.medium.com/max/379/1*J95osu37vPxJcB98DBYvZQ.png)

All the magic happens in the  **handleSubmit** callback:

![](https://miro.medium.com/max/628/1*BEtmEWqdGK3keWfuVDpHCQ.png)

-   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_](https://developer.mozilla.org/en-US/docs/Web/API/FormData)_)_

# `<TextInput />`

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

You can see the  [code here](https://codesandbox.io/s/simple-form-validation-ghxg1m?file=/src/components/textInput.tsx), 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:

![](https://miro.medium.com/max/526/1*ZxSlDwNLcDUcccTlnaprXQ.png)

# Implementation

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

![](https://miro.medium.com/max/700/1*8YmgnL-tBrs6TkWyp_jlRg.png)

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](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel))

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, lets 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:

![](https://miro.medium.com/max/341/1*O6l2jRb-OSEDNGnfrKgWtg.png)

# i18n

One great thing to keep in mind is that the constraint API validation messages are localised 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 customisation via the  **errorText** attribute:

![](https://miro.medium.com/max/596/1*Z-XfopKaoLTvEASg_EEI2Q.png)

This is just an example to show how one could customise those messages.

![](https://miro.medium.com/max/308/1*y2ZBh26eDDPxhNkaBdzlcg.png)

# Finally

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

We receive a  **FormData**  object which can then be easily sent using the  [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/fetch)  or  [XMLHttpRequest.send()](https://developer.mozilla.org/en-US/docs/Web/API/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 in more familiar key/value object:

![](https://miro.medium.com/max/504/1*kPCl15rJ-u-JMtimxheqHg.png)

to produce the following:

![](https://miro.medium.com/max/487/1*-0U1nHRwfFJC2M4ki0HV3w.png)

# 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](https://dev.to/cuginoale/form-validationsubmission-in-vanilla-react-part-2-3gok)_