React Form Validations Tutorial

Understanding the Problem

We need to add validation to our ContactUs form to ensure that users provide valid data before submitting. Specifically, we need to:

Devising a Plan

  1. Add state variables to track validation errors and form submission attempts
  2. Create a useEffect hook to validate the form fields and update validation errors
  3. Modify the form submission handler to check for validation errors
  4. Display validation error messages in the UI
  5. Test the validation functionality

Carrying Out the Plan

Step 1: Add Validation State

First, we need to add state variables to track validation errors and whether the form has been submitted:

// Add these imports and state variables to ContactUs.jsx
import { useState, useEffect } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');
  
  // New state variables for validation
  const [validationErrors, setValidationErrors] = useState({});
  const [hasSubmitted, setHasSubmitted] = useState(false);

  // Rest of the component...
}

Step 2: Add Validation Logic Using useEffect

Next, we'll create a useEffect hook that runs whenever the name or email fields change:

// Add this useEffect hook after the state variables
useEffect(() => {
  const errors = {};
  
  // Validate name field
  if (!name.length) errors.name = 'Please enter your Name';
  
  // Validate email field
  if (!email.includes('@')) errors.email = 'Please provide a valid Email';
  
  // Update validation errors state
  setValidationErrors(errors);
}, [name, email]);

Step 3: Update Form Submission Handler

Now, let's modify the onSubmit function to handle validation errors:

const onSubmit = e => {
  // Prevent the default form behavior so the page doesn't reload.
  e.preventDefault();
  
  // Set hasSubmitted to true to show validation errors
  setHasSubmitted(true);
  
  // Check if there are any validation errors
  if (Object.values(validationErrors).length) {
    return alert(`The following errors were found:
    
      ${validationErrors.name ? "* " + validationErrors.name : ""}
      ${validationErrors.email ? "* " + validationErrors.email : ""}`);
  }

  // Create a new object for the contact information.
  const contactUsInformation = {
    name,
    email,
    phone,
    phoneType,
    comments,
    submittedOn: new Date()
  };

  // Log the contact information to the console.
  console.log(contactUsInformation);

  // Reset the form state.
  setName('');
  setEmail('');
  setPhone('');
  setPhoneType('');
  setComments('');
  setValidationErrors({});
  setHasSubmitted(false);
};

Step 4: Display Validation Error Messages

Finally, let's update the form JSX to display error messages:

// Update the name input field section
<div>
  <label htmlFor='name'>Name:</label>
  <input
    id='name'
    type='text'
    onChange={e => setName(e.target.value)}
    value={name}
  />
  <div className='error'>
    {hasSubmitted && validationErrors.name && `* ${validationErrors.name}`}
  </div>
</div>

// Update the email input field section
<div>
  <label htmlFor='email'>Email:</label>
  <input
    id='email'
    type='text'
    onChange={e => setEmail(e.target.value)}
    value={email}
  />
  <div className='error'>
    {hasSubmitted && validationErrors.email && `* ${validationErrors.email}`}
  </div>
</div>

Complete ContactUs Component with Validation

Here's the complete ContactUs component with all validations implemented:

import { useState, useEffect } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');
  const [validationErrors, setValidationErrors] = useState({});
  const [hasSubmitted, setHasSubmitted] = useState(false);

  useEffect(() => {
    const errors = {};
    if (!name.length) errors.name = 'Please enter your Name';
    if (!email.includes('@')) errors.email = 'Please provide a valid Email';
    setValidationErrors(errors);
  }, [name, email]);

  const onSubmit = e => {
    // Prevent the default form behavior so the page doesn't reload.
    e.preventDefault();
    
    setHasSubmitted(true);
    if (Object.values(validationErrors).length) {
      return alert(`The following errors were found:
      
        ${validationErrors.name ? "* " + validationErrors.name : ""}
        ${validationErrors.email ? "* " + validationErrors.email : ""}`);
    }

    // Create a new object for the contact information.
    const contactUsInformation = {
      name,
      email,
      phone,
      phoneType,
      comments,
      submittedOn: new Date()
    };

    // Ideally, you'd persist this information to a database using a RESTful API.
    // For now, though, just log the contact information to the console.
    console.log(contactUsInformation);

    // Reset the form state.
    setName('');
    setEmail('');
    setPhone('');
    setPhoneType('');
    setComments('');
    setValidationErrors({});
    setHasSubmitted(false);
  }

  return (
    <div>
      <h2>Contact Us</h2>
      <form onSubmit={onSubmit}>
        <div>
          <label htmlFor='name'>Name:</label>
          <input
            id='name'
            type='text'
            onChange={e => setName(e.target.value)}
            value={name}
          />
          <div className='error'>
            {hasSubmitted && validationErrors.name && `* ${validationErrors.name}`}
          </div>
        </div>
        <div>
          <label htmlFor='email'>Email:</label>
          <input
            id='email'
            type='text'
            onChange={e => setEmail(e.target.value)}
            value={email}
          />
          <div className='error'>
            {hasSubmitted && validationErrors.email && `* ${validationErrors.email}`}
          </div>
        </div>
        <div>
          <label htmlFor='phone'>Phone:</label>
          <input
            id='phone'
            type='text'
            onChange={e => setPhone(e.target.value)}
            value={phone}
          />
          <select
            name='phoneType'
            onChange={e => setPhoneType(e.target.value)}
            value={phoneType}
          >
            <option value='' disabled>
              Select a phone type...
            </option>
            <option>Home</option>
            <option>Work</option>
            <option>Mobile</option>
          </select>
        </div>
        <div>
          <label htmlFor='comments'>Comments:</label>
          <textarea
            id='comments'
            name='comments'
            onChange={e => setComments(e.target.value)}
            value={comments}
          />
        </div>
        <button>Submit</button>
      </form>
    </div>
  );
}

export default ContactUs;

Looking Back

Understanding Form Validation in React

Let's break down the key concepts from our implementation:

1. State Management for Validation

We used two state variables for validation:

2. Real-time Validation with useEffect

We used a useEffect hook to:

3. Conditional Error Display

We only show error messages after the form has been submitted once:

{hasSubmitted && validationErrors.name && `* ${validationErrors.name}`}

This pattern uses the logical AND (&&) operator to conditionally render error messages only when:

4. Form Submission Prevention

We prevent form submission when validation errors exist by:

Client-Side vs. Server-Side Validation

It's important to understand the limitations of client-side validation:

Best practice is to implement both:

Real-World Applications

Form validation is essential in nearly all web applications:

Validation Techniques in Production

In real-world applications, you might use more advanced validation techniques:

Enhanced Validation Examples

Here are some examples of more sophisticated validation techniques:

1. Regular Expression for Email Validation

// More robust email validation
if (!/\S+@\S+\.\S+/.test(email)) {
  errors.email = 'Please provide a valid email address';
}

2. Phone Number Formatting

// Validate phone number format
if (phone && !/^\d{10}$/.test(phone.replace(/\D/g, ''))) {
  errors.phone = 'Please enter a 10-digit phone number';
}

3. Conditional Required Fields

// Phone type is required if phone is provided
if (phone && !phoneType) {
  errors.phoneType = 'Please select a phone type';
}

4. Character Count Limitations

// Limit comment length
if (comments.length > 500) {
  errors.comments = 'Comments must be less than 500 characters';
}

Further Learning

To continue improving your form validation skills, consider exploring: