Form Validation in React.js using React Functional Components and React Hooks

Posted by: Ravi Kiran , on 2/13/2021, in Category Reactjs
Views: 1204300
Abstract: This React.js tutorial will get you started with Forms in React.js by building a simple form and and showing how to perform validations on the form fields. The examples in the article use React functional components and React hooks.

Good user interfaces are amongst the most challenging aspects of modern application development.

One of the most important aspects of getting the UI right is the handling of user input. As React developers, we should know how to handle forms in a React application.

This tutorial will get you started with the forms in React.js by building a simple form and validating the values.

This article will cover how to work with input controls in React. We will then build a simple form in React and show how to perform validations on the form fields.

The examples in the article are built using React functional components and React hooks.

Working with Input Controls in React.js

In a React component, state is the data context for the views. It is natural to assign the values received from form fields into the state. This can be done by listening to the change events on the input controls and assigning the values received to the state.

The following snippet shows how this is done in a React component:

import React, { useState } from 'react';
     
export function SampleInput() {
  const      [name, setName] = useState('Ravi');
     
  function handleChange(e)  {
    e.preventDefault();
     
    setName(e.target.value);
  };
     
  return (
     <div>
        Name:
        <input
          type='text'
          value={name}
          onChange={handleChange}
        />
      </div>
      <div>Name is: {name}</div>
               
  );
}

Listing 1: A React component with input text field

As we can see, the value of the input text field is set using a value in state, and the state is updated on the onChange event handler using the value present in the text field. This way, two-way binding is established and the value shown in the div element below the text field gets updated on whenever a key is pressed in the textbox. The following screenshot shows how this works:

textbox-two-way-binding

Figure 1 – textbox with 2-way binding

The input element controlled this way is called a “controlled component”. You can read more about controlled component in the React documentation.

Note: Notice the way the handleChange method is defined in the Listing 1. It is created as an arrow function to retain the current class context. Otherwise, the method will get the context of the input field and the instance variables won’t be accessible.

The input fields can also be managed using refs. A ref can be created using useRef hook. The ref attribute assigned on an element receives DOM object of the element. The properties and events of the element are accessible using the DOM object.

The following snippet shows the modified version of the SampleComponent using refs:

import React, { useState, useRef, useEffect } from 'react';

export function SampleInput() {
  const      [name, setName] = useState('Ravi');
  const      input = useRef(     );

  useEffect(() => {
    input.current.onkeyup = handleChange;
    input.current.value = name;
  });
  
  function handleChange(e) {
    e.preventDefault();

    setName(e.target.value);
  }

  return (
    <React.Fragment     >
      <div>
        Name:
        <input type='text' ref={input} />
      </div>
      <div>Name is: {name}</div>
    </React.Fragment>
  );
}

Listing 2: Using ref with input element

Here the value and the event handler are assigned to the input element in the useEffect hook. This is because the component has to be rendered and the DOM objects have to be ready before we use it to access the value or listen to the events.

The input element handled this way is called “uncontrolled component”. You can read more about uncontrolled components in the React documentation.

This component produces the same result as the one shown in Listing 1.

We will be using the controlled component approach in the next section of the article as it integrates better with the state. Also, it is the recommended way to build forms in a React application.

Building a Sign-up form in React.js

Let’s build a sign-up form and validate the fields to learn more. Let’s create a component with a few fields to accept the inputs from a user to sign up. Listing 3 shows the initial state of the component with the fields and the state:

import React, { useState } from 'react';
import { SampleInput } from './SampleInput';
import logo from './logo.svg';
import './App.css';
import {
  minMaxLength,
  validEmail,
  passwordStrength,
  userExists,
} from './validations';
                                        
function App() {
  const      [user, setUser] = useState({});
  const      [formErrors, setFormErrors] = useState({});

                                                            

       
    return (
      <div className='App container col-6'>
        <h3>New User Registration Form</h3>
        <form noValidate>
          <div className='row'>
            <div className='col-md-6'>
              <label htmlFor='firstName'>First Name</label>
              <input
                className='form-control'
                placeholder='First Name'
                type='text'
                name='firstName'
                noValidate
              />
            </div>
            <div className='col-md-6'>
              <label htmlFor='lastName'>Last Name</label>
              <input
                className='form-control'
                placeholder='Last Name'
                type='text'
                name='lastName'
                noValidate
              />
            </div>
          </div>

          <div className='mb-3'>
            <label htmlFor='email'>Email</label>
            <input
              className='form-control'
              placeholder='Email'
              type='email'
              name='email'
              noValidate
            />
          </div>
          <div className='mb-3'>
            <label htmlFor='password'>Password</label>
            <input
              className='form-control'
              placeholder='Password'
              type='password'
              name='password'
              noValidate
            />
          </div>
          <div className='mb-3'>
            <label htmlFor='confirmpassword'>Confirm Password</label>
            <input
              className='form-control'
              placeholder='Password'
              type='password'
              name='confirmpassword'
              noValidate
            />
          </div>
          <div className='mb-3'>
            <button type='submit'>Create Account</button>
          </div>
        </form>
      </div>
    );
       
}

Listing 3: Initial component with signup form

The above form consists of fields to enter the first name, last name, e-mail ID, password and to confirm the password. Notice the noValidate attribute used on the form and on the input elements; it is done to prevent any default HTML5 validations by the browser. Figure 3 shows how it looks when rendered on the browser:

reactjs-user-reg-form

Figure 3: A sign-up form

Let’s add event handlers to all these input controls. It is possible to create a single function and assign it to all the controls. The function has to assign values to the respective state fields based on the source input element.

Following is the function to be used for the event handler:

Function handleChange (e){
  const { name, value } = e.target;
  let formErrors = this.state.formErrors;

  switch (name) {
    case 'firstName':
      setUser({ ...user, firstName: value });
               
      break;
    case 'lastName':
                     
setUser({ ...user, lastName: value });
      break;
    case 'email':
      setUser({ ...user, email: value });
               
      break;
    case 'password':
      setUser({ ...user, password: value });
               
      break;
    case 'confirmpassword':
      setUser({ ...user, confirmpassword: value });
               
      break;
    default:
      break;
  }
}

Listing 4: Event handler for input fields

The event handler can be assigned on the input elements to handle the onBlur event. Alternatively, the onKeyUp event may also be used but it will keep assigning the state on every key stroke. To avoid that we are going to use onBlur. Listing 5 shows how to assign it to one of the fields:

<input
  className= 'form-control'
  placeholder='First Name'
  type='text'
  name='firstName'
  noValidate
  onBlur={handleChange}
/>

Listing 5: Input with event handler

Now the value entered in the text box would get updated in the state field when a user moves the focus away from the input element accepting the first name. The same change can be applied on the other input elements.

To see if the values are assigned to the state, the state object can be printed on a console when the form is submitted.

With this, the form is ready for accepting inputs and passing them on to the application.

Let’s add now validations to it!

 

Form Validations in React.js

The values received in the input fields can be validated on the change event handler. We will be adding the following validations to the sign-up form:

● First name and last name have to contain at least 3 characters

● Password has to contain at least 6 characters

● E-mail ID has to be valid

● Confirm password has to match password

● Password should be strong

● E-mail ID has to be unique

Note: The validations added here would be running on the client side. It is important to perform these validations on the server as well, as client-side validations can get compromised by hackers or JavaScript could be turned off in the browsers.

Let’s create generic helper functions to perform these validations. Listing 5 shows the functions to check minimum length, format of the e-mail ID and to check if the e-mail ID is unique:

export function minMaxLength(text, minLength, maxLength) {
    let result = !text || text.length < minLength;
    if(maxLength)
        result = result || text.length < minLength;
    return result;
}

export function validEmail(text) {
    const regex = RegExp(
        /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
      );
    
    return !regex.test(text);
}

let registeredUsers = [
    'ravi@kiran.com',
    'mail@myblog.in',
    'contact@lucky.com'
];


export function userExists(email) {
    return new Promise(resolve => {
        setTimeout(() => {
            if(registeredUsers.findIndex(u => u === email) !== -1) {
                resolve(true);
            }
            else {
                resolve(false);
            }
        });
    });
}

Listing 5: Validation functions

The function userExists in Listing 5 returns a promise and it checks for the existence of the e-mail ID in a hard-coded array. In real applications, this has to be replaced with an API call.

Writing logic to check strength of a password is a big task. Let’s take the help of a library to do that. Install the package zxcvbn using the following command:

> npm install zxcvbn

This library exposes a function that accepts the password string and returns a result. We can determine whether the password is strong or not, using the score it returns. Listing 6 imports the library and performs the validation:

import * as zxcvbn from 'zxcvbn';

export function passwordStrength(text) {
    let result = zxcvbn(text);
    return result.score < 3;
}

Listing 6: Password strength validation

Now let’s use these validations in the onBlur event handler. Listing 7 shows the validation of the field for the first name:

case 'firstName':
  if (minMaxLength(value, 3)) {
    currentFormErrors[
      name
    ] = `First Name should have minimum 3 characters`;
  } else {
    delete currentFormErrors[name];
  }
  break;

Listing 7: Validation for first name

The Last name can be validated in a similar way. As Listing 7 shows, the error message to be shown is set to the formErrors object. This object will be used to show the validation messages and to disable the submit button. When the field is valid, it deletes the error of the field from the object.

The E-mail ID needs to be checked for format and for uniqueness. As the userExists validation function returns a promise to simulate the behavior of an API call, the validity has to be set in the success callback of the promise. Listing 8 shows this validation:

case 'email':
  if (!value || validEmail(value)) {
    currentFormErrors[name] = `Email address is invalid`;
  } else {
    userExists(value).then((result) => {
      if (result) {
        currentFormErrors[name] =
          'The email is already registered. Please use a different email.';
      } else {
        delete currentFormErrors[name];
      }
    });
}

Listing 8: E-mail validation

The password field has to be checked for length and for strength. A value entered in the Confirm Password field has to be matched with password field and if it doesn’t match, an error has to be assigned.

The field Confirm Password needs to be validated when the password field is changed as well, as the user may decide to change the password entered, before submitting. The following snippet shows the validation of the two password fields:

case 'password':
  if (minMaxLength(value, 6)) {
    currentFormErrors[name] = 'Password should have minimum 6 characters';
  } else if (passwordStrength(value)) {
    currentFormErrors[name] =
      'Password is not strong enough. Include an upper case letter, a number or a special character to make it strong';
  } else {
    delete currentFormErrors[name];
    setUser({
      ...user,
      password: value,
    });
    if (user.confirmpassword) {
      validateConfirmPassword(
        value,
        user.confirmpassword,
        currentFormErrors
      );
    }
  }
  break;

case 'confirmpassword':
  let valid = validateConfirmPassword(
    user.password,
    value,
    currentFormErrors
  );
  if (valid) {
    setUser({ ...user, confirmpassword: value });
  }
 break;

Listing 9: Validation of Password and confirm password fields

As the confirm password validation has to be performed twice, it is better to put it in a separate function. The following snippet shows the function validateConfirmPassword:

function validateConfirmPassword(
  password,
  confirmpassword,
  formErrors
) {
  formErrors = formErrors || {};
  if (password !== confirmpassword) {
    formErrors.confirmpassword =
      'Confirmed password is not matching with password';
    return false;
  } else {
    delete formErrors.confirmpassword;
    return true;
  }
}

Listing 10: validateConfirmPassword function

The last change to be made is to set validations in the state. It is done in the following snippet:

setFormErrors(currentFormErrors);

Now that the validations are in place, the user needs to be notified about them. Let’s apply some styles on the fields to show that the field has an invalid value. The following snippet modifies the style of the field for first name:

<input
  className={
    formErrors && formErrors.firstName
      ? 'form-control error'
      : 'form-control'
  }
  placeholder='First Name'
  type='text'
  name='firstName'
  noValidate
  onBlur={handleChange}
/>

Listing 11: Error style applied to first name

The attribute className is modified to assign the error class when the field has a validation error. The same change can be applied to other fields also. The CSS class error is set to the following style to show a red border on the input elements:

.app      input.error {
  border: 1px solid red;
}

Listing 12: Error style

There are different ways to show error messages. They can be either shown below the input elements, listed in an unordered list, displayed as tooltips on the input elements or any other way your UX designer suggests.

In the demo, we will show it as an unordered list. For this, we need to iterate over the fields of formErrors object and print the messages in a list. Listing 12 shows how to achieve this:

<ul>
  {Object.entries(formErrors || {}).map(([prop, value]) => {
    return (
      <li className='error-message' key={prop}>
        {value}
      </li>
    );
  })}
</ul>

Listing 12: List of errors

The only pending todo now is to disable the submit button when the form has errors. This can be done by checking if formErrors has any entries. See Listing 13.

<button
  type='submit'
  disabled={Object.entries(formErrors || {}).length > 0}
>
  Create Account
</button>

Listing 13: Disabling the button

Now when there are invalid values, the form will start showing errors and marking the fields with a red border. A state of the form is shown in Figure 4:

form-validation-errors

Figure 4: Form with validation errors

Conclusion

Handling forms is an important part of any business application and it can turn tricky at times. This tutorial shows how to handle simple forms in React and perform validations. Hope this tutorial got you started with Forms in React. Reach out to me at my twitter handle @sravi_kiran with any comments or questions.

This article was technically reviewed by Benjamin Jakobus.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!