You don’t need a fancy library when you have HTML5 and Constraint API. The official documentation suggests 3 possible ways to handle form submission/validation: React 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 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. ref Either way, we are left with the nontrivial task of implementing the logic to validate and collect the form data. These days there are that do exactly that... but plenty of 3rd party libraries 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: and are mandatory Name Email should represent a valid email address Email is optional, no constraints The address is optional but if entered it should represent a formally correct UK phone number Tel 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 : HTML5 already has the ability to validate most user data without relying on JavaScript. This is done by using . (Source ) MDN validation attributes on form elements : Specifies whether a form field needs to be filled in before the form can be submitted. required and : Specifies the minimum and maximum length of textual data (strings). minlength maxlength and : Specifies the minimum and maximum values of numerical input types. min max : Specifies whether the data needs to be a number, an email address, or some other specific preset type. type : Specifies a that defines a pattern the entered data needs to follow. pattern regular expression 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 , 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. Constraint Validation API 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 attribute turns off the browser's automatic validation error messages. Without that our form would look like this: novalidate All the magic happens in the callback: handleSubmit 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 callback, if valid onSubmit The last one is another key point of this solution: collecting the form data using the object. There is no need to manually go through the form elements and extract their values. FormData “The interface provides a way to easily construct a set of key/value pairs representing form fields and their values” —( FormData ) 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: code here Implementation The code to implement the form in the example looks like this: Things to notice: We set the attribute on and required Name Email makes sure Constraint API checks the input is a valid email address type=”email” as no validation rules Address is not but we set the attribute to check the input against the regex (setting doesn’t enforce any ) Tel required pattern type=tel built-in validation Whenever we need to add another field to the form we just add a new and we set the relevant attributes to it. TextInput No need to update schemas or add yet another to track the value of the new field, let’s just offload the heavy lifting to the built-in API! useState Improving the UX The final touch to improve the UX is to show validation errors only after the form gets submitted. Using just the 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! :invalid For this reason, we add the className to the form and style the TextInput with this: .submitted 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 component provided in the example allows also for basic validation message customization via the attribute: TextInput errorText 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 callback. onSubmit We receive a object which can then be easily sent using the or method. It uses the same format a form would use if the encoding type were set to , or we can transform it into a more familiar key/value object: FormData fetch() XMLHttpRequest.send() "multipart/form-data" 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