React Form: Real Time Validation With Browser API

The great thing about React is that it give developers almost total freedom in how to handle application logic. In this extra tutorial, I will explore how to handle form validation in React Using browser's built-in validation API.

*This is a side tutorial of me exploring React real time validation using Browser API. For validating form fields the "React Way", checkout this post

Starting Code

We will continue with the starter code from the previous post, with the email validation solution using regex.

Our goal is to give appropriate error messages as user type into our input fields. We will do two things:

  1. Highlight the field with error using red border around it
  2. Display error text below the field

React form documentation doesn't explain how to do this stuff, so it's actually up to developers how they want to do form validations. I'm covering two ways of doing validation in React, and this one is actually not the recommended way. This is just me having fun exploring React, so tag along if you have free time. Else, React Real Time Validation Using State might be what you're looking for.

Validation Logic Using Browser's Validation API

Did you know that all modern browsers have validation API built-in that can be used to validate input element? Before using React, I didn't. But since working with React, I discovered some interesting features built into browsers. A part of Bootstrap documentation use this same API for creating custom validation style, so we're going to use it as our reference.

This method require us to turn off browser's automatic validation, so let's add noValidate attribute into our form.

<form onSubmit={this.handleSubmit} noValidate>

Next, we will add html attributes that will be used by browser's built-in validation to determine the validity of our input elements:

  1. Add an id for each label element so we can use the label text content in our validation method
  2. Add a div with id of [label]-error below our inputs that will contain validation error messages
  3. We are adding ref attributes so our React app can check the validity of our inputs
  4. Finally, we'll add regex pattern for email check only for email input

Let's update our form input children as follows:

<div className="form-group">
  {/* add id to labels*/}
  <label htmlFor="email" id="email-label">Email address</label>
  <input
    className="form-control"
    id="name"
    name="email"
    ref="email"
    type="email"
    pattern="^[^\s@]+@[^\s@]+\.[^\s@]{2,}$"
    placeholder="Enter email"
    value={this.state.email}
    onChange={this.handleChange}
    required
  />
  {/* error div with Bootstrap invalid-feedback class */}
  <div className="invalid-feedback" id="email-error" />
</div>

<div className="form-group">
  <label htmlFor="username" id="username-label">Username</label>
  <input
    className="form-control"
    id="username"
    name="username"
    ref="username"
    type="text"
    placeholder="Enter username"
    value={this.state.username}
    onChange={this.handleChange}
    required
  />
  <div className="invalid-feedback" id="username-error" />
</div>

<div className="form-group">
  <label htmlFor="password" id="password-label">Password</label>
  <input
    className="form-control"
    id="password"
    name="password"
    ref="password"
    type="password"
    placeholder="Enter password"
    value={this.state.password}
    onChange={this.handleChange}
  />
  <div className="invalid-feedback" id="password-error" />
</div>        

Next, we have to add another method that will check the values of our input elements. This is almost identical to canSubmit function, with the exception that this function has to display feedback to our UI in the form of error messages.

It will be nice to display errors as user type into our input, so let's add a function inside our handleChange method. This function will require the name of our input so it can access the DOM element and retrieve its value.

handleChange(event) {
  this.setState({
    [event.target.name]: event.target.value
  }, function(){ this.canSubmit()})
  
  //check error in our input
  this.checkInputError(event.target.name);    
}

Using Refs To Access The DOM in React

Next, let's create this new checkInputError that we just call in our handleChange method. It will use the input name argument to get our input element through its ref attribute. This is the reason why we put the same value in name and ref attribute of our input elements. If the value between name and ref are different, this method will not work. Using ref attribute, we will check the validity of our input element and then display error message in accordance with the right validity properties.

checkInputError(refName) {
  //retrieving things from our DOM
  const validity = this.refs[refName].validity
  const label = document.getElementById(`${refName}-label`).textContent
  const error = document.getElementById(`${refName}-error`)

  // check validity object
  if (!validity.valid) {
    // error found, show error message and invalid class to field
    error.textContent = `${label} is invalid`    
    this.refs[refName].classList.add('is-invalid')
    return
  }  
      
  // no error, remove invalid class
  this.refs[refName].classList.remove('is-invalid')
  error.textContent = ''
  return
}

In the code above, we're using the refName parameter to retrieve the input validity object, the input label content, and the error div as well for displaying our errors. If the validity.valid property returns false, we will display a dynamic error message showing the input field is invalid. then we add a class into our input field named is-invalid, which is a Bootstrap class that highlight our input field red.

Now let's try out this method in our form by typing into our email input field. You'll see that the error message will be displayed below the input field.

Making Error Message Specific

We have successfully created a method that checks our input validity and display error message if it's invalid, so let's take it a step further and make the error message more specific to our input field. This can be done easily by using our validity object properties. For example, validity.valueMissing will return true if the element has no value but is a required field.

checkInputError(refName) {
  const validity = this.refs[refName].validity
  const label = document.getElementById(`${refName}-label`).textContent
  const error = document.getElementById(`${refName}-error`)

  if (!validity.valid) {
    // validity is not valid, so we will display specific errors now.
    if (validity.valueMissing) {
      error.textContent = `${label} cannot be empty`
    } else if (validity.patternMismatch) {
      error.textContent = `${label} should be a valid email address`
    } 
    
    this.refs[refName].classList.add('is-invalid')
    return
  }  
      
  this.refs[refName].classList.remove('is-invalid')
  error.textContent = ''
  return
}

The validity.patternMismatch property above returns true if the element's value doesn't match the provided pattern. This is why we add a pattern attribute to our email field. We've created specific error message for our email field, so let's do the same thing for our password field.

Custom Validity For The Password Input

checkInputError(refName) {
  const validity = this.refs[refName].validity
  const label = document.getElementById(`${refName}-label`).textContent
  const error = document.getElementById(`${refName}-error`)
  const isPassword = refName === 'password'

  if (isPassword) {
    if (this.refs.password.value.length < 5) {
      this.refs.password.setCustomValidity('should be 5 characters or more')
    } else {
      this.refs.password.setCustomValidity('')
    }
  }

  if (!validity.valid) {
    if (validity.valueMissing) {
      error.textContent = `${label} is a required field`
    } else if (validity.patternMismatch) {
      error.textContent = `${label} should be a valid email address`
    } else if (isPassword && validity.customError) {
      error.textContent = `${label} should be 5 characters or more`
    } 
    
    this.refs[refName].classList.add('is-invalid')
    return
  }  
      
  this.refs[refName].classList.remove('is-invalid')
  error.textContent = ''
  return
}

All good to go! Now we have validation error messages properly setup for our input elements.

Conclusion

React leaves a lot of blank space that developers can fill the way they want to. It's up to us how we'd like to implement validation logic into our forms. The preferred method is to use React state, but browser's built-in validation API can work as well. When I first started this tutorial, I didn't know a thing about ValidyState object. So when I discover it, I can't resist but to share it in this tutorial 😂

Like what you read? I will do more posts on writing forms in React. Do follow me so that you'll be notified when it's up 🌞

Oh, and here's the finished tutorial source code


Get my weekly newsletter ✉️
Let's explore the exciting world of React together.
No Spam. Unsubscribe anytime