How to use HTML5 form validations with React Js

You’re probably getting tired of reinventing the wheel like me whose always coding a validation check in each field whether it follows the correct format like emails, or whether first and last names have values in it. What’s worse when you’re using React and you’re just a first-timer in the ecosystem, you have the tendency to think of form validations the React way. We don’t want verbose, lengthy implementations as much as possible for basic functionality.

So, for those who were still having trouble in setting up their form validations in cleaner, elegant way, I hope you may find this article helpful in your use-case as I got into the same painful situation of looking for an elegant way to validate my form inputs.

What this tutorial does and who is this for

This tutorial aims and leverages your existing knowledge in HTML5 Constraint API Validation and React as much as possible so that you can get started doing it yourself right away.

This is for developers who don’t want to use and adopt any existing libraries because they create a black box in your form validation for React Apps alone. If that’s the case, you’ve come to the right place.

This is for developers who don’t want too much setup in field validations, making it clean and minimal as possible.

Coding field validations programmatically yourself can be tedious work, repetitive because this is already a solved problem and we already have an API for this, and most especially your code could be prone to errors if not managed properly. After all, we’re dealing with user input forms for most of our work as web developers. Isn’t it? I remembered a lot of bloated logic for form validations I made back then in jQuery days and it was horrible where I can simply look for a simple solution that solves the problem.

Forms were one of the fundamental functionalities in any web applications we make, and equally as complex sometimes. So we spend most of our times adding input field elements and code some validation checks on it which our work could get tedious and at times very lengthy for just basic functionality.

Before you delve deeper in this tutorial, there’s already an existing solution for this

You probably know by now that there’s already a couple of solutions, libraries available to solve the tedious work of Form validations for React Ecosystem such as, Redux FormFormik, and probably some others I’m not aware of. This goes to show that form validations were actually a big deal for most developers to start with that some of them try to standardize form validations which could be a good thing. Although they may make your code validations much streamlined and make your life easier, you might want fine-grained control over how are you going to do the validations yourself and save it in your component state for later use.

So, if you want to get your hands dirty and validate forms on your own, let’s get this started.

Getting Started

First off, we’re going to make use of Constraint Validation API which is the API mechanism of HTML5, for the most part, to ease our validation issues in the form. You can get more detailed information here at Mozilla’s official documentation here.

Add input, validation labels, and state in React Component

Using <form> element to enclose our input fields

We first need to make sure that our input fields are enclosed within form element. Put this inside render function:

render() {
return (<form
    className="form-inside-input"
    onSubmit={this.onSubmit}
    noValidate
>
    {/* form inputs here */}
</form>)
}

Notice our noValidate attribute being added in form field. This is to ensure that we’re disabling HTML5 validations by default and access Constraint API programmatically to check for validations. If we enable HTML5 validations, we have little control of the look and feel of error messages and where we should put it.

and our onSubmit function:

onSubmit = e => {
    e.preventDefault();
};

We need to make sure that e.preventDefault is present to discard default behaviors of onSubmit event.

Adding input fields and validation labels inside our <form> element

Now that we have gone ahead and set our form elements including onSubmit function, we need to add input fields inside of it so that we can start validating user inputs.

Add this code snippet inside your <form> element:

<input
    type="email"
    name="txtEmail"
    placeholder="Email"
    required
/>
<br />
<input
    type="text"
    name="txtFname"
    placeholder="First Name"
    required
/>
<br />
<input
    type="text"
    name="txtLname"
    placeholder="Last Name"
    required
/>
<br />

Note the required attribute here. If we are to validate input forms, we must include this attribute so Constraint API will know that it needs to be validated (e.g. field must not be empty).

Input fields should be named accordingly:

  • firstname
  • lastname
  • email

This is one necessary step we need to keep in mind so that we can access specific validation flags of that particular element later on.

Setting up our state

We also need to organize how our state should be, what are its properties and respective objects to show/hide input forms. And when the user was finally finished with filling up forms we show the “success” UI, informing users that their input is correct and is successfully validated.

This is how it should look like:

const txtFieldState = {
    value: "",
    valid: true,
    typeMismatch: false,
    errMsg: "" //this is where our error message gets across
};
class App extends Component {
    state = {
        email: { ...txtFieldState, fieldName: "Email", required: true, requiredTxt: "Email is required", formatErrorTxt: "Incorrect email format" },
        firstname: { ...txtFieldState, fieldName: "First Name", required: true, requiredTxt: "First Name is required" },
        lastname: { ...txtFieldState, fieldName: "Last Name", required: false, requiredTxt: "Last Name is required" },
        allFieldsValid: false
    };
}

Let’s explain each property in state object

For email, firstname, lastname:

  • value — value of user input
  • valid — whether the input is valid or not. If the input is empty or doesn’t follow the input format properly, it will return false
  • typeMismatch — this returns false if the input format is not followed(such as email, numbers-only input format). But returns true if all good
  • fieldName — name of the field
  • required — You can use this property to set whether the input field is required or not. We can add logic in our react components that check whether we should make use of required input field or not
  • requiredTxt — Error message that shows up when user doesn’t add any input
  • formatErrorTxt — Error message that shows up when user inputs an incorrect format such as incorrect email format or zip code

And lastly:

  • allFieldsValid — we make use of this property when all fields are valid then display a success message or any other logic you needed to do

We need to name the input fields the same as our state. This is an important process because we’re going to access the javascript DOM and retrieve state based on input field name. More on that on the next step.

Populating onSubmit function to validate user input

Now we’re going to populate our onSubmit function for validating user input. Copy and paste this inside your App component:

//we need to extract specific properties in Constraint Validation API using this code snippet
reduceFormValues = formElements => {
    const arrElements = Array.prototype.slice.call(formElements); //we convert elements/inputs into an array found inside form element
//we need to extract specific properties in Constraint Validation API using this code snippet
    const formValues = arrElements
        .filter(elem => elem.name.length > 0)
        .map(x => {
            const { typeMismatch } = x.validity;
            const { name, type, value } = x;
return {
                name,
                type,
                typeMismatch, //we use typeMismatch when format is incorrect(e.g. incorrect email)
                value,
                valid: x.checkValidity()
            };
        })
        .reduce((acc, currVal) => { //then we finally use reduce, ready to put it in our state
            const { value, valid, typeMismatch } = currVal;
            const {
                fieldName,
                requiredTxt,
                formatErrorTxt
            } = this.state[currVal.name]; //get the rest of properties inside the state object
//we'll need to map these properties back to state so we use reducer...
            acc[currVal.name] = {
                value,
                valid,
                typeMismatch,
                fieldName,
                requiredTxt,
                formatErrorTxt
            };
return acc;
        }, {});
return formValues;
}
checkAllFieldsValid = (formValues) => {
    return !Object.keys(formValues)
        .map(x => formValues[x])
        .some(field => !field.valid);
};
onSubmit = e => {
    e.preventDefault();
    const form = e.target;
const formValues = this.reduceFormValues(form.elements);
    const allFieldsValid = this.checkAllFieldsValid(formValues);
    
    //note: put ajax calls here to persist the form inputs in the database.
//END
this.setState({ ...formValues, allFieldsValid }); //we set the state based on the extracted values from Constraint Validation API
};
//END
this.setState({ ...formValues, allFieldsValid }); //we set the state based on the extracted values from Constraint Validation API
};

We’re now making use of Constraint API in the code snippet, particularly in reduceFormValues function. Here are a few things you should take note in Constraint API:

  • type — gets the input type (text, email, etc.)
  • checkValidity() — This checks whether the input is valid. If it has an incorrect format or the required fields are empty, it returns false
  • typeMismatch — Use this flag when the format is incorrect (e.g. email). This flag is useful when dealing with input fields like email. And we don’t need to come up with our own regex validations for that matter

You can find out more info on them here:

Note: I should add though that you noticed that we need to use native javascript objects to validate and get the validation flags whether the user adds value or does the format right in input field. We’re potentially violating React’s design patterns while doing so as we’re accessing the DOM properties using native javascript. We have no other choice but access the DOM properties since we’re making use of Constraint API to let it do the validation for us instead of creating validation checks on our own. Another approach is to use Refs from React. But I don’t see this as beneficial nor helpful than just directly accessing the elements from onSubmit event argument.

Adding error message label in each user input

Let’s now add labels to display errors when there are validation errors on each field.

Create ErrorValidationLabel function:

const ErrorValidationLabel = ({ txtLbl }) => (
    <label htmlFor="" style={{ color: "red" }}>
        {txtLbl}
    </label>
);

And use this inside render function:

render() {
    const { email, firstname, lastname, allFieldsValid } = this.state;
const renderEmailValidationError = email.valid ? 
        "" : 
        <ErrorValidationLabel txtLbl={email.typeMismatch ? email.formatErrorTxt : email.requiredTxt} />;
    const renderDateValidationError = lastname.valid ? "" : <ErrorValidationLabel txtLbl={lastname.requiredTxt} />;
    const renderFnameValidationError = firstname.valid ? "" : <ErrorValidationLabel txtLbl={firstname.requiredTxt} />;
return (
        <>
            <div className="form-input">
                <h1 style={{ textAlign: "center" }}>
                    React / HTML5 Form Validation
                </h1>
                <form
                    className="form-inside-input"
                    onSubmit={this.onSubmit}
                    noValidate
                >
                    <input type="email" name="email" placeholder="Email" required />
                    <br />
                    {renderEmailValidationError}
                    <br />
                    <input type="text" name="firstname" placeholder="First Name" required />
                    <br />
                    {renderFnameValidationError}
                    <br />
                    <input type="text" name="lastname" placeholder="Last Name" required />
                    <br />
                    {renderDateValidationError}
                    <br />
<input type="submit" value="Submit" />
                </form>
            </div>
        </>
    );
}

Adding success message in the UI

When all fields are correct, we can add additional UI that shows all user input is valid and they’ve successfully submitted the form.

Copy-paste this render function inside your component:

render() {
    const { email, firstname, lastname, allFieldsValid } = this.state;
    const successFormDisplay = allFieldsValid ? "block" : "none";
    const inputFormDisplay = !allFieldsValid ? "block" : "none";
const renderEmailValidationError = email.valid ? 
        "" : 
        <ErrorValidationLabel txtLbl={email.typeMismatch ? email.formatErrorTxt : email.requiredTxt} />;
    const renderDateValidationError = lastname.valid ? "" : <ErrorValidationLabel txtLbl={lastname.requiredTxt} />;
    const renderFnameValidationError = firstname.valid ? "" : <ErrorValidationLabel txtLbl={firstname.requiredTxt} />;
return (
        <>
            <div style={{display: successFormDisplay}}>
                <h1 style={{ textAlign: "center" }}>Success!</h1>
                <p style={{ textAlign: "center" }}>
                    You have successfully submitted a form.
                </p>
            </div>
<div className="form-input" style={{display: inputFormDisplay}}>
                <h1 style={{ textAlign: "center" }}>
                    React / HTML5 Form Validation
                </h1>
                <form
                    className="form-inside-input"
                    onSubmit={this.onSubmit}
                    noValidate
                >
                    <input type="email" name="email" placeholder="Email" required />
                    <br />
                    {renderEmailValidationError}
                    <br />
                    <input type="text" name="firstname" placeholder="First Name" required />
                    <br />
                    {renderFnameValidationError}
                    <br />
                    <input type="text" name="lastname" placeholder="Last Name" required />
                    <br />
                    {renderDateValidationError}
                    <br />
<input type="submit" value="Submit" />
                </form>
            </div>
        </>
    );
}

Add a bit of styling using CSS

We can further add some styling in our sample form validation using css.

Copy-paste this inside your App.css to finish things up:

.form-input {
    margin: 0 auto;
    height: 500px;
    width: 500px;
}
.form-inside-input {
    height: 500px;
    width: 200px;
    margin: 0 auto;
}

Wrapping things up

If you followed everything correctly so far, this is how your form validation should look like by now including the source code:

Conclusion

There are already lots of great solutions on validating our forms without too much effort. And this is one of them. Form validations should not be a pain for web developers.

Now you have at least some bare minimum to get your form validations up and running. There are still a lot of things you can do while using Constraint Validation API. Try to play with it and see how it plays well in your validation forms in React.

Let me know if you’re having trouble with the tutorial in the comments section. Here’s the github repo for this sample:


Bonus

You might not satisfied with the solution I presented above and you probably crave for additional tweakings out of this solution. If you’re still interested to find out, read on.

Refactoring Input Components

You might not look like how our input components are as repetitive as it seems. If you’re not satisfied the way we code our input components, then we can leverage React’s approach to make our code minimal and less repetitive. We can improve this by using arrays and maps to reduce the repetitiveness of our components.

So, let’s get this started.

  1. Create Field Component

Paste this code snippet outside of your App Component:

const Field = ({ valid, type, fieldId, fieldName, typeMismatch, formatErrorTxt, requiredTxt }) => {
    const renderErrorLabel = !valid ? <ErrorValidationLabel txtLbl={typeMismatch ? formatErrorTxt : requiredTxt} /> : "";
return (
        <>
            <input type={type} name={fieldId} placeholder={fieldName} required />
            <br/>
            {renderErrorLabel}
            <br/>
        </>
    );
}

2. Convert State Properties into an array

We’ll next need to convert our state properties into an array so that we can map them as Field components. Paste this code snippet inside your App Component:

mapFieldInputs = () =>{
    //we filter out `allFieldsValid` property as this is not included state for our input fields
    return Object.keys(this.state).filter(x => x !== "allFieldsValid").map(field => {
        return {
            fieldId: field,
            ...this.state[field]
        };
    });
}

3. Using it in render function

This is how your render function will look like after the refactoring:

render() {
    const { allFieldsValid } = this.state;
    const successFormDisplay = allFieldsValid ? "block" : "none";
    const inputFormDisplay = !allFieldsValid ? "block" : "none";
    const fields = this.mapFieldInputs();
const renderFields = fields.map(x => <Field {...x} />);
return (
        <>
            <div style={{display: successFormDisplay}}>
                <h1 style={{ textAlign: "center" }}>Success!</h1>
                <p style={{ textAlign: "center" }}>
                    You have successfully submitted a form.
                </p>
            </div>
<div className="form-input" style={{display: inputFormDisplay}}>
                <h1 style={{ textAlign: "center" }}>
                    React / HTML5 Form Validation
                </h1>
                <form
                    className="form-inside-input"
                    onSubmit={this.onSubmit}
                    noValidate
                >
                    {renderFields}
<input type="submit" value="Submit" />
                </form>
            </div>
        </>
    );
}

4. Setting it up altogether

This is how the code looks like after we’re done refactoring our input fields. The code also runs like a charm:

That’s all folks!

A Software Engineer who loves to code and write articles.

Leave a reply:

Your email address will not be published.

Site Footer