DEV Community

Chinenye Oluyede
Chinenye Oluyede

Posted on

Mastering Form Validation in React

Imagine spending 30 minutes filling out a job application, only to be told your submission failed because you entered an invalid email address. The frustration can be overwhelming, especially if the error message is vague and unhelpful. In this article, we'll explore the essential concept of form validation in React, a very important skill for ensuring a smooth user experience in web applications. We'll walk through setting up a form component, implementing validation logic, displaying error messages, and even exploring popular libraries that can simplify the process.

Prerequisites

  • HTML and CSS knowledge

  • Javascript fundamentals

  • Basic react knowledge (Including state management and event handling)

What is Form Validation?

Form validation is a way of ensuring that data entered in a form is accurate, complete, and aligns with specific rules and conditions.

Benefits of form validation

  • It ensures data integrity and accuracy.

  • It improves user experience by providing immediate and clear feedback on what needs to be changed.

  • It prevents users from submitting invalid or harmful data.

Core concepts of form validation

  • Required fields

  • Format validation (e.g Email, phone numbers)

  • Length Validation (Minimum and Maximum characters)

  • Pattern validation (passwords)

  • Custom validation logic (Specific rules based on the application's needs)

In this article, we're going to be considering the first four.

Different approaches to form validation in react

  • Using built-in HTML5 validation attributes.

  • Implementing custom validation logic in Javascript.

  • React hooks for state management and validation.

  • Form Validation libraries like Formik, React Hook Form, Redux Form, etc.

In this article, we'll be using react hooks.

Implementing form validation using react hooks

Before we dive into the implementation, I assume that you have already created your React app. If you haven't, I suggest doing so using Create React App for a quick and easy start. You can set up your project by running the following commands in your terminal:

npx create-react-app dynamic-form
cd dynamic-form
npm start
Enter fullscreen mode Exit fullscreen mode

Once your app is up and running, we can proceed to create the form component.

Creating the form component

First, we'll set up the basic form structure using a functional component. This form should include basic inputs like username, email address, and password, which we will validate.

In your src folder, create a file called DynamicForm.js (or whatever you wish to call it), and create a component using the same name.

import React from "react";

function DynamicForm() {
  return (
    <div>
      <h1>Dynamic Form</h1>
    </div>
  );
}

export default DynamicForm;

Enter fullscreen mode Exit fullscreen mode

NB: Clean up the app.js file and import the DynamicForm component there to display the component.
Your app.js file should look like this:

import "./App.css";
import DynamicForm from "./DynamicForm";

function App() {
  return (
    <div className="App">
      <DynamicForm />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Next, we'll create our form component with the basic inputs

import React from "react";

function DynamicForm() {
  return (
    <div>
      <h1>Dynamic Form</h1>
      <form action="">
        <div>
          <label htmlFor="username">Username</label>
          <br />
          <input type="text" name="name" placeholder="name" />
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <br />
          <input type="text" name="email" placeholder="email" />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <br />
          <input type="text" name="password" placeholder="********" />
        </div>
        <div>
          <label htmlFor="confirmPassword">Password</label>
          <br />
          <input type="text" name="confirmPassword" placeholder="********" />
        </div>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default DynamicForm;

Enter fullscreen mode Exit fullscreen mode

Next, we'll initialize state for our form fields using the useState Hook

import React, { useState } from "react";

function DynamicForm() {
  const [errors, setErrors] = useState({});

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
  const [isFormValid, setIsFormValid] = useState(false);

  return (
    <div>
      <h1>Dynamic Form</h1>
      <form action="">
        <div>
          <label htmlFor="username">Username</label>
          <br />
          <input type="text" name="name" placeholder="name" />
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <br />
          <input type="text" name="email" placeholder="email" />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <br />
          <input type="text" name="password" placeholder="********" />
        </div>
        <div>
          <label htmlFor="confirmPassword">Password</label>
          <br />
          <input type="text" name="confirmPassword" placeholder="********" />
        </div>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default DynamicForm;


Enter fullscreen mode Exit fullscreen mode

Next, we'll create an event handler to handle changes made to our inputs, and add the handleChange method to our inputs.

import React, { useState } from "react";

function DynamicForm() {
  const [errors, setErrors] = useState({});

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
  const [isFormValid, setIsFormValid] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };
  return (
    <div>
      <h1>Dynamic Form</h1>
      <form action="">
        <div>
          <label htmlFor="username">Username</label>
          <br />
          <input
            type="text"
            name="name"
            placeholder="name"
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <br />
          <input
            type="text"
            name="email"
            placeholder="email"
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <br />
          <input
            type="text"
            name="password"
            placeholder="********"
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="confirmPassword">Password</label>
          <br />
          <input
            type="text"
            name="confirmPassword"
            placeholder="********"
            onChange={handleChange}
          />
        </div>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default DynamicForm;

Enter fullscreen mode Exit fullscreen mode

Next, we'll define the function that validates the inputs. We can call that validateField. It will take two parameters: name and value.

  const validateField = (name, value) => {};

Enter fullscreen mode Exit fullscreen mode

Now, we'll set validation conditions for the inputs. These are the conditions we'll set:

  • Username must be 3 characters and above.
  • Email must be a valid email address.
  • Password must be more than 8 characters and contain a number.
  • Confirm Password must match the password.

We will use the switch operator to set the conditions, for a simpler format.

import React, { useState } from "react";

function DynamicForm() {
  const [errors, setErrors] = useState({});
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
  const [isFormValid, setIsFormValid] = useState(false);

  const validateField = (name, value) => {
    const validationErrors = { ...errors };

    switch (name) {
      case "name":
        if (value.length < 3) {
          validationErrors.name = "Name must be more than 3 characters.";
        } else {
          delete validationErrors.name;
        }
        break;

      case "email":
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
          validationErrors.email = "Email is not valid";
        } else {
          delete validationErrors.email;
        }
        break;

      case "password":
        if (value.length < 8) {
          validationErrors.password = "Password must be more than 8 characters";
        } else if (!/\d/.test(value)) {
          validationErrors.password = "Password must contain a number";
        } else {
          delete validationErrors.password;
        }
        break;

      case "confirmPassword":
        if (value != formData.password) {
          validationErrors.confirmPassword = "Passwords must match";
        } else {
          delete validationErrors.password;
        }
      break;

      default:
        break;
    }
     setErrors(validationErrors)
  };
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };
  return (
    <div>
      <h1>Dynamic Form</h1>
      <form action="">
        <div>
          <label htmlFor="username">Username</label>
          <br />
          <input
            type="text"
            name="name"
            placeholder="name"
            onChange={handleChange}
            value={formData.name}
          />
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <br />
          <input
            type="text"
            name="email"
            placeholder="email"
            onChange={handleChange}
            value={formData.email}
          />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <br />
          <input
            type="password"
            name="password"
            placeholder="********"
            onChange={handleChange}
            value={formData.password}
          />
        </div>
        <div>
          <label htmlFor="confirmPassword">Password</label>
          <br />
          <input
            type="password"
            name="confirmPassword"
            placeholder="********"
            onChange={handleChange}
            value={formData.confirmPassword}
          />
        </div>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default DynamicForm;

Enter fullscreen mode Exit fullscreen mode

Now, we've set the conditions, let's add it to the form to make sure the inputs are checked before the form can be submitted.

First, we'll create a handleSubmit function to add to the submit button.

const handleSubmit = (e) => {
    e.preventDefault();
  };
Enter fullscreen mode Exit fullscreen mode

Next, we'll use the useEffect hook to manage the validation logic and update the form's validity status when there are changes in the state

  useEffect(() => {
    setIsFormValid(
      Object.keys(errors).length === 0 &&
        formData.name.length >= 3 &&
        /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
        formData.password.length >= 8 &&
        /\d/.test(formData.password) &&
        formData.confirmPassword === formData.password
    );
  }, [errors, formData]);

Enter fullscreen mode Exit fullscreen mode

Don't forget to import useEffect at the top of your file.
Next, add this line to the handleChange function:

 validateField(name, value);

Enter fullscreen mode Exit fullscreen mode

Next, in the input section, we'll add the error messages below the input.

import React, { useState, useEffect } from "react";

function DynamicForm() {
  const [errors, setErrors] = useState({});
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
  const [isFormValid, setIsFormValid] = useState(false);

  const validateField = (name, value) => {
    const validationErrors = { ...errors };

    switch (name) {
      case "name":
        if (value.length < 3) {
          validationErrors.name = "Name must be more than 3 characters.";
        } else {
          delete validationErrors.name;
        }
        break;

      case "email":
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
          validationErrors.email = "Email is not valid";
        } else {
          delete validationErrors.email;
        }
        break;

      case "password":
        if (value.length < 8) {
          validationErrors.password = "Password must be more than 8 characters";
        } else if (!/\d/.test(value)) {
          validationErrors.password = "Password must contain a number";
        } else {
          delete validationErrors.password;
        }
        break;

      case "confirmPassword":
        if (value != formData.password) {
          validationErrors.confirmPassword = "Passwords must match";
        } else {
          delete validationErrors.password;
        }
        break;

      default:
        break;
    }

    setErrors(validationErrors);
  };
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: value,
    });
    validateField(name, value);
  };

  useEffect(() => {
    setIsFormValid(
      Object.keys(errors).length === 0 &&
        formData.name.length >= 3 &&
        /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
        formData.password.length >= 8 &&
        /\d/.test(formData.password) &&
        formData.confirmPassword === formData.password
    );
  }, [errors, formData]);

  const handleSubmit = (e) => {
    e.preventDefault();

    if (isFormValid) {
      const dataString = `
      Name: ${formData.name}
      Email: ${formData.email}
      Password: ${formData.password}
      Confirm Password: ${formData.confirmPassword}
      `;

      alert(`Form Submitted Successfully!\n${dataString}`);
    }
  };
  return (
    <div>
      <h1>Dynamic Form</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="username">Username</label>
          <br />
          <input
            type="text"
            name="name"
            placeholder="name"
            onChange={handleChange}
            value={formData.name}
          />
          {errors.name && <p style={{ color: "red" }}>{errors.name}</p>}
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <br />
          <input
            type="text"
            name="email"
            placeholder="email"
            onChange={handleChange}
            value={formData.email}
          />
          {errors.name && <p style={{ color: "red" }}>{errors.email}</p>}
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <br />
          <input
            type="password"
            name="password"
            placeholder="********"
            onChange={handleChange}
            value={formData.password}
          />
          {errors.name && <p style={{ color: "red" }}>{errors.password}</p>}
        </div>
        <div>
          <label htmlFor="confirmPassword">Password</label>
          <br />
          <input
            type="password"
            name="confirmPassword"
            placeholder="********"
            onChange={handleChange}
            value={formData.confirmPassword}
          />
          {errors.name && (
            <p style={{ color: "red" }}>{errors.confirmPassword}</p>
          )}
        </div>

        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default DynamicForm;


Enter fullscreen mode Exit fullscreen mode

At this point, you should see your error messages as you type in your inputs.

Next, we want to get an alert with the inputs if the form was submitted successfully.
Add this line of code to the handleSubmit function

 if (isFormValid) {
      const dataString = `
      Name: ${formData.name}
      Email: ${formData.email}
      Password: ${formData.password}
      Confirm Password: ${formData.confirmPassword}
      `;

      alert(`Form Submitted Successfully!\n${dataString}`);
    }
Enter fullscreen mode Exit fullscreen mode

Finally, we want to disable the submit button until all fields are valid.

 <button type="submit" disabled={!isFormValid}>
    Submit
 </button>
Enter fullscreen mode Exit fullscreen mode

And everything should work as expected now! You should get your error messages as you type, and they should clear when you meet the conditions.

Libraries that can simplify the process

If you don't want to go through that whole process, here are some form validation libraries you can explore some that can handle it.

  1. React Hook Form
  2. Formik
  3. React Final Form
  4. Redux Form

Key Takeaways

  • Importance of Form Validation: Proper validation not only prevents invalid data from being submitted but also guides users in filling out forms correctly, reducing frustration and improving overall satisfaction.
  • State Management: Utilizing React's useState hook allows us to manage form data and validation errors effectively, providing a seamless user experience.
  • Real-Time Feedback: Implementing real-time validation enhances user interaction by providing immediate feedback, making forms more intuitive.
  • Using Libraries: For more complex forms, consider leveraging libraries like Formik or React Hook Form to simplify validation and state management.

Final Thoughts

Form validation is a crucial aspect of web development that every React developer should master. By implementing effective validation strategies, you can create forms that not only function well but also provide a positive experience for users.
I encourage you to experiment with the concepts discussed in this article and explore additional validation techniques and libraries to further enhance your forms.
If you have any questions or would like to share your experiences with form validation in React, feel free to leave a comment below. Happy coding!

Top comments (0)