January 2, 2023

React Form Creation and Validation Using React Hooks

In this post we'll see how to create a form in React using React hooks. When you are creating a form, you need to take care of the following-

  • Of course, creating a form with the fields that are needed.
  • Getting the entered value. Once you have access to entered values you can validate the values and to do further processing like saving those values in a database.
  • You may have different states for form inputs like input is valid or input is invalid.
  • Showing error message after input validation to let user know what is wrong with the entered value.
  • Overall form is valid or not. If one or more inputs are invalid then overall form is also considered invalid and, in that case, you should not let user submit it. If all inputs are valid then form is also considered valid.

React form example

As an example, we'll take a user registration form with 4 fields- name, email, password, confirmed password.

First step is to create a basic form with the above-mentioned fields. I am not doing any styling to keep focus on the task in hand creating a form in React.

const UserRegistration = () => {
  return (
    <form>
      <h2>User Registration</h2>
      <div>
        <label htmlFor='name'>User Name: </label>
        <input type='text' id='name' />
      </div>
      <div>
        <label htmlFor='email'>Email: </label>
        <input type='email' id='email' />
      </div>
      <div>
        <label htmlFor='password'>Password: </label>
        <input type='password' id='password' />
      </div>
      <div>
        <label htmlFor='cnfpassword'>Confirmed Password :</label>
        <input type='password' id='cnfpassword' />
      </div>
      <button>Submit</button>
    </form>
  );
}

export default UserRegistration;

Retrieving entered values - Adding state

Since there are four fields in the form so we need to manage 4 states. Here again we have certain decisions to make, first is; to use state or not. If you use state to manage fields then your component is a controlled component. Another option is to use ref which makes your component an uncontrolled component.

Since React documentation suggests to use controlled components (in most cases) to implement forms so we'll stick to it.

Another decision is to have separate individual states for fields or have a single object with all the fields as object properties. In this example we'll go with an object as that makes it easy to track the state change (You can do it with a single method rather than having as many methods as fields in your form).

import { useState } from "react";

const UserRegistration = () => {
  const [values, setValues] = useState({
    name: '',
    email: '',
    password: '',
    cnfpassword: ''
  });

  const onChangeHandler = (event) => {
    let name = event.target.name;
    let value = event.target.value;
    setValues(inputs => ({...inputs, [name]: value}))
  }

  const resetFormValues = () => {
    setValues({
      name: '',
      email: '',
      password: '',
      cnfpassword: ''});
  }

  const onSubmitHandler = (event) => {
    event.preventDefault();
    console.log("name ", values.name, "email ", values.email);
    resetFormValues();
  }
  return (
    <form onSubmit={onSubmitHandler}>
      <h2>User Registration</h2>
      <div>
        <label htmlFor='name'>User Name: </label>
        <input type='text' id='name' name='name' value={values.name} onChange={onChangeHandler}/>
      </div>
      <div>
        <label htmlFor='email'>Email: </label>
        <input type='email' id='email' name='email' value={values.email} onChange={onChangeHandler}/>
      </div>
      <div>
        <label htmlFor='password'>Password: </label>
        <input type='password' id='password' name='password' value={values.password} onChange={onChangeHandler}/>
      </div>
      <div>
        <label htmlFor='cnfpassword'>Confirmed Password :</label>
        <input type='password' id='cnfpassword' name='cnfpassword' value={values.cnfpassword} onChange={onChangeHandler}/>
      </div>
      <button>Submit</button>
    </form>
  );
}

export default UserRegistration;
As you can see form code has changed quite a bit and some important addition to the forms are-
  • Inclusion of useState() hook with initial state as an object with four properties, one for each form fields. Initial value for these object properties is blank as all of them are string values.
      const [values, setValues] = useState({
        name: '',
        email: '',
        password: '',
        cnfpassword: ''
      });
    
  • When we say we are managing the state of the form fields that means state has to be updated with the new value whenever input field changes. That's what onChangeHandler function does, it sets the new value for the changed field in the state. That's where having the initial state as object is beneficial, we can have a single function to take care of state update for any field. First take all the values using spread operator (…), then update the changed value by passing the name:value pair.
      const onChangeHandler = (event) => {
        let name = event.target.name;
        let value = event.target.value;
        setValues(inputs => ({...inputs, [name]: value}))
      }
    
  • In order to make it work you need to add onChange event handler to each input field. Also add the name and value attributes and set the value to the matching property of the values object (which is defined in useState).
    <input type='text' id='name' name='name' value={values.name} onChange={onChangeHandler}/>
    
  • If you were using separate states for each field same thing would have been done as
    const [nameValue, setNameValue] = useState(‘’);
    const [emailValue, setEmailValue] = useState(‘’);
    ...
    …
    const nameValueHandler = (event) => {
      setNameValue(event.target.value);
    }
    const EmailValueHandler = (event) => {
      setEmailValue(event.target.value);
    }
    
  • Another addition is the onSubmitHandler() function which is triggered when the form is submitted. It is just logging the entered name and email. event.preventDefault(); has to be called to avoid browser reload which is the default action when form is submitted.
  • When form is submitted resetFormValues() function is called to reset the fields to blank again.

At this stage you should be able to enter values in the form and show the name and email values after submitting the form. On submitting form fields should also reset to blank values.

React User Registration form example

React form - Adding validation

At this stage you have a form where values can be entered and form can be submitted. Another important task is to validate the form fields and also display a message telling user what is wrong with the entered input if input fails validation.

Validation can be done for all the fields when user submits the form or you can also validate a field as soon as user tabs out of that field.

Validating and displaying message as soon as user enters input and moves on to another field is considered better because you can provide instant feedback and give user a chance to rectify the mistake rather than waiting till the end and doing validation when user submits the form.

To validate a field as soon as user tabs out, you can use onBlur() event handler. You can manage state to find out whether a particular field is visited or not which can then be changed in onBlur() event handler. For example to check if name field is visited or not you can add following state.

const [isNameFieldTouched, setNameFieldTouched] = useState(false);
which is changed in blur event handler. 
const nameBlurHandler = () => {
  setNameFieldTouched(true);
}

onBlur() event is added in the name input field.

<input 
  type='text' 
  id='name' 
  name='name' 
  value={values.name} 
  onChange={onChangeHandler} 
  onBlur={nameBlurHandler}/>
  {!isNameValid && isNameFieldTouched && <span className="error">Name field must not be left blank</span>}

You also need to add your validation logic for the form fields as per the requirement. In this example it is done in a separate file.

Validators.js

export const nameValidator = (name) => {
    if (name.trim() === ''){
        return false;
    }
    return true;
}

export const emailValidator = (email) => {
    if (email.trim() === ''){
        return false;
    }
    const emailRegex = new RegExp(/^[A-Za-z0-9_!#$%&'*+\/=?`{|}~^.-]+@[A-Za-z0-9.-]+$/);
    if(!emailRegex.test(email)){
        return false;
    }
    return true;
}

export const passwordValidator = (password) => {
    if (password.trim().length < 8){
        return false;
    }
    return true;
}

export const cnfPasswordValidator = (cnfPassword, password) => {
    if (cnfPassword.trim().length < 8 || cnfPassword !== password){
        return false;
    }
    return true;
}

Modified user registration form with the validation logic also added.

UserRegistration.js

import { useState } from "react";
import { cnfPasswordValidator, emailValidator, nameValidator, passwordValidator } from "./Validators";
import './form.css';

const UserRegistration = () => {
  const [values, setValues] = useState({
    name: '',
    email: '',
    password: '',
    cnfpassword: ''
  });
  const [isNameFieldTouched, setNameFieldTouched] = useState(false);
  const isNameValid = nameValidator(values.name);
  const [isEmailFieldTouched, setEmailFieldTouched] = useState(false);
  const isEmailValid = emailValidator(values.email);
  const [isPwdFieldTouched, setPwdFieldTouched] = useState(false);
  const isPwdValid = passwordValidator(values.password);
  const [isCnfpwdFieldTouched, setCnfpwdFieldTouched] = useState(false);
  const isCnfpwdValid = cnfPasswordValidator(values.cnfpassword, values.password);
  
  const nameBlurHandler = () => {
    setNameFieldTouched(true);
  }
  const emailBlurHandler = () => {
    setEmailFieldTouched(true);
  }
  const passwordBlurHandler = () => {
    setPwdFieldTouched(true);
  }
  const cnfPasswordBlurHandler = () => {
    setCnfpwdFieldTouched(true);
  }
  // Checking overall form validity - used to enable disable submit button
  let isFormValid = false;
  if(isNameValid && isEmailValid && isPwdValid && isCnfpwdValid){
    isFormValid = true;
  }

  const onChangeHandler = (event) => {
    let name = event.target.name;
    let value = event.target.value;
    setValues(inputs => ({...inputs, [name]: value}))
  }

  const resetFormValues = () => {
    setValues({
      name: '',
      email: '',
      password: '',
      cnfpassword: ''});
  }

  const onSubmitHandler = (event) => {
    event.preventDefault();
    console.log("name ", values.name, "email ", values.email);
    resetFormValues();
    setNameFieldTouched(false);
    setEmailFieldTouched(false);
    setPwdFieldTouched(false);
    setCnfpwdFieldTouched(false);
  }
  return (
    <form onSubmit={onSubmitHandler}>
      <h2>User Registration</h2>
      <div>
        <label htmlFor='name'>User Name: </label>
        <input 
          type='text' 
          id='name' 
          name='name' 
          value={values.name} 
          onChange={onChangeHandler} 
          onBlur={nameBlurHandler}/>
          {!isNameValid && isNameFieldTouched && <span className="error">Name field must not be left blank</span>}
      </div>
      <div>
        <label htmlFor='email'>Email: </label>
        <input 
          type='email' 
          id='email' 
          name='email' 
          value={values.email} 
          onChange={onChangeHandler}
          onBlur={emailBlurHandler}/>
        {!isEmailValid && isEmailFieldTouched && <span className="error">Valid email required</span>}
      </div>
      <div>
        <label htmlFor='password'>Password: </label>
        <input 
          type='password' 
          id='password' 
          name='password' 
          value={values.password} 
          onChange={onChangeHandler}
          onBlur={passwordBlurHandler}/>
          {!isPwdValid && isPwdFieldTouched && <span className="error">Password of length atleast eight required</span>}
      </div>
      <div>
        <label htmlFor='cnfpassword'>Confirmed Password :</label>
        <input 
          type='password' 
          id='cnfpassword' 
          name='cnfpassword' 
          value={values.cnfpassword} 
          onChange={onChangeHandler}
          onBlur={cnfPasswordBlurHandler}/>
          {!isCnfpwdValid && isCnfpwdFieldTouched && <span className="error">Confirmed password should match the password</span>}
      </div>
      <button disabled={!isFormValid}>Submit</button>
    </form>
  );
}

export default UserRegistration;

CSS styling for validation messages.

form.css

.error {
  color: red;
}
  1. For each field a blur handler method is added so there are 4 blur handler methods setting whether the corresponding field is touched or not.
  2. Also check the entered value by validating it using the method written in Validator.js.
    const isNameValid = nameValidator(values.name);
    
  3. Based on whether field is touched or not and whether entered value is valid or not you can display a message.
     
    {!isNameValid && isNameFieldTouched && <span className="error">Name field must not be left blank</span>}
    
    If field is visited and value is not valid then the above message is displayed.
  4. Also check the form validity. Form is considered valid if all the fields are valid.
    let isFormValid = false;
    if(isNameValid && isEmailValid && isPwdValid && isCnfpwdValid){
      isFormValid = true;
    }
    
  5. Use isFormValid value to disable or enable submit button.
    <button disabled={!isFormValid}>Submit</button>
    
  6. Once form is submitted set all the touched states to false.
      const onSubmitHandler = (event) => {
        event.preventDefault();
        console.log("name ", values.name, "email ", values.email);
        resetFormValues();
        setNameFieldTouched(false);
        setEmailFieldTouched(false);
        setPwdFieldTouched(false);
        setCnfpwdFieldTouched(false);
      }
    
React form with validation
React form example

That's all for the topic React Form Creation and Validation Using React Hooks. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment