Module: Forms and Controlled Components

Form Handling

React JS: Forms and Controlled Components - Form Handling

Introduction

Forms are a fundamental part of most web applications. React provides a powerful and flexible way to handle forms using controlled components. This approach gives React complete control over the form data, making it easier to validate, manipulate, and display. This document will cover the core concepts of form handling in React, focusing on controlled components.

What are Controlled Components?

In a controlled component, the React state is the "single source of truth" for the form's data. Instead of the DOM directly handling the form's value, React manages it. Here's how it works:

  1. State: Form input values are stored in the component's state.
  2. value Prop: The value prop of the input element is bound to the corresponding state variable.
  3. onChange Event: The onChange event handler is used to update the state whenever the input value changes.

This means every keystroke in the input field triggers an update to the component's state, and React re-renders the input with the new value.

Basic Form Example

Let's start with a simple example of a controlled component for a text input:

import React, { useState } from 'react';

function MyForm() {
  const [name, setName] = useState('');

  const handleChange = (event) => {
    setName(event.target.value);
  };

  return (
    <form>
      <label htmlFor="name">Name:</label>
      <input
        type="text"
        id="name"
        value={name}
        onChange={handleChange}
      />
      <p>You entered: {name}</p>
    </form>
  );
}

export default MyForm;

Explanation:

  • useState(''): Initializes a state variable name with an empty string. This will hold the input value.
  • handleChange(event): This function is called whenever the input value changes.
    • event.target.value: Accesses the current value of the input field.
    • setName(event.target.value): Updates the name state variable with the new value.
  • value={name}: Binds the input's value prop to the name state variable. This ensures the input always displays the current state value.
  • onChange={handleChange}: Attaches the handleChange function to the input's onChange event.

Handling Multiple Inputs

To handle multiple inputs, you'll need to manage multiple state variables. You can use a single object to store all the input values for better organization.

import React, { useState } from 'react';

function MultiInputForm() {
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    email: '',
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData({
      ...formData,
      [name]: value,
    });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form Data:', formData);
    // You can send the formData to an API here
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="firstName">First Name:</label>
      <input
        type="text"
        id="firstName"
        name="firstName"
        value={formData.firstName}
        onChange={handleChange}
      />
      <br />

      <label htmlFor="lastName">Last Name:</label>
      <input
        type="text"
        id="lastName"
        name="lastName"
        value={formData.lastName}
        onChange={handleChange}
      />
      <br />

      <label htmlFor="email">Email:</label>
      <input
        type="email"
        id="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <br />

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

export default MultiInputForm;

Explanation:

  • useState({ ... }): Initializes a state object formData with properties for each input field.
  • handleChange(event):
    • const { name, value } = event.target;: Extracts the name and value attributes from the event target (the input field).
    • setFormData({ ...formData, [name]: value });: Updates the formData state object. The spread operator (...formData) creates a copy of the existing state, and then the [name]: value syntax dynamically updates the property corresponding to the input's name attribute.
  • handleSubmit(event): Prevents the default form submission behavior and logs the form data to the console. This is where you would typically send the data to an API.
  • name attribute: Crucially, each input field has a name attribute that matches the corresponding property in the formData state object. This is how the handleChange function knows which state property to update.

Handling Different Input Types

The same principles apply to other input types like checkbox, radio, select, and textarea.

  • Checkbox: The value of a checkbox is typically a boolean. The checked attribute is used instead of value.

    <input
      type="checkbox"
      id="agree"
      checked={formData.agree}
      onChange={(e) => setFormData({...formData, agree: e.target.checked})}
    />
    
  • Radio: Radio buttons share the same name attribute. The value attribute determines which option is selected.

    <input
      type="radio"
      id="male"
      name="gender"
      value="male"
      checked={formData.gender === 'male'}
      onChange={(e) => setFormData({...formData, gender: e.target.value})}
    />
    
  • Select: The value prop of the select element is bound to the state. The onChange handler updates the state with the selected option's value.

    <select value={formData.country} onChange={(e) => setFormData({...formData, country: e.target.value})}>
      <option value="us">United States</option>
      <option value="ca">Canada</option>
      <option value="uk">United Kingdom</option>
    </select>
    
  • Textarea: Works similarly to a text input, but uses the textarea element.

    <textarea value={formData.message} onChange={(e) => setFormData({...formData, message: e.target.value})} />
    

Form Validation

Controlled components make form validation much easier. You can validate the input values as they change or when the form is submitted.

import React, { useState } from 'react';

function ValidatedForm() {
  const [formData, setFormData] = useState({
    email: '',
  });
  const [error, setError] = useState('');

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData({ ...formData, [name]: value });
    setError(''); // Clear any previous error
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      setError('Invalid email address');
    } else {
      console.log('Valid email:', formData.email);
      // Submit the form
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email:</label>
      <input
        type="email"
        id="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

export default ValidatedForm;

Explanation:

  • error state: Stores any validation error messages.
  • Validation logic: The handleSubmit function includes a regular expression to validate the email address.
  • Error display: If the email is invalid, the error state is updated, and an error message is displayed below the input field.
  • Clearing Errors: The handleChange function clears the error message on each input change, providing immediate feedback.

Benefits of Controlled Components

  • Predictability: React manages the form state, making the data flow predictable.
  • Validation: Easy to