ReactJS

React Forms

Build accessible and engaging forms in React. Understand form fundamentals, implement clear validation, and ensure a seamless user experience.
Table of Contents

The Importance of Forms in React

Forms are fundamental building blocks for any interactive web application. They allow users to provide input, crucial for tasks like logins, registrations, search queries, and submitting data. In React applications, forms play a vital role in capturing this user input and enabling features that rely on it. By effectively handling form submissions and the data users enter, you can create dynamic and user-driven experiences within your React projects.

Example of React Form for Capturing a Username

function ProductOrder() {
  const [quantity, setQuantity] = useState(1);

  const handleChange = (event) => {
    const newQuantity = parseInt(event.target.value);
    if (newQuantity >= 1) {
      setQuantity(newQuantity);
    }
  };

  return (
    <form>
      <label>
        Quantity:
        <input type="number" value={quantity} onChange={handleChange} />
      </label>
    </form>
  );
}

Explanation

  • const [quantity, setQuantity] = useState(1);: Creates a state variable quantity to store the selected product quantity, initially set to 1.
  • <input type="number" value={quantity} onChange={handleChange} />:
    • Creates a number input field that displays the current quantity state value.
    • The onChange attribute triggers the handleChange function whenever the user types in the input field.
  • handleChange: This function validates and updates the quantity state variable:
    • It parses the input value to a number (parseInt).
    • It checks if the new quantity is valid (greater than or equal to 1) before updating the state with setQuantity.

React’s Approach: Controlled Components

In React, forms are typically handled using a concept called controlled components. This approach ensures that the form data is always controlled by the React component’s state, providing a more predictable and manageable way to work with form submissions and user input. Here’s a step-by-step breakdown of controlled components, with a username input field:

  1. Manage Form Data with State: Create state variables using useState to store the values for each form element. In this example, we create a state variable username to store the entered username.
const [username, setUsername] = useState("");
  1. Bind Input Values to State: Set the value attribute of each form element to the corresponding state variable, ensuring the displayed value reflects the current state. The input element’s value is set to the username state variable.
<input type="text" value={username} onChange={handleChange} />
  1. Handle User Input with Event Handlers: Define functions (onChange for most input types) to capture changes within the form elements. We define the handleChange function to capture changes in the username input.
  2. Update State Based on Input: Inside the event handler functions, update the corresponding state variables with the new values from the form element (event.target.value). The handleChange function updates the username state with the new value entered by the user.
const handleChange = (event) => {
  setUsername(event.target.value);
};

Following these steps, you link the username input element, its displayed value (username), and the underlying React state (username). This allows you to effectively manage user input within your React forms using controlled components.

Complete Code

function UsernameForm() {
  const [username, setUsername] = useState("");

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

  return (
    <form>
      <label>
        Username:
        <input type="text" value={username} onChange={handleChange} />
      </label>
    </form>
  );
}

Explanation

  • const [username, setUsername] = useState("");: Creates a state variable username to store the username (initially empty).
  • <input type="text" value={username} onChange={handleChange} />:
    • Sets the input field’s value to the username state variable, displaying the current username.
    • Attaches the onChange event handler to capture changes.
  • handleChange: Updates the username state with the new value from the input field (event.target.value).

Essential Form Elements

React forms use various input types, such as text fields, checkboxes, and dropdown menus, to collect different kinds of data from the user. The onSubmit event lets you capture and process this data when the user submits the form. This allows you to send the form data to a server, update your application, or perform other actions based on user input.

Input Types

React forms offer a variety of input elements for different user input needs. Here’s a quick overview of some common input types:

  • Text Inputs: Single-line fields for entering short text data, like names, email addresses, or short descriptions.
  • Text Areas: Multi-line fields ideal for capturing longer text content, such as reviews, comments, or detailed descriptions.
  • Checkboxes: Allow users to select multiple options from a set of choices. Each checkbox has an on/off state.
  • Radio Buttons: Used for selecting a single option from a group of mutually exclusive choices. Only one radio button can be selected at a time.
  • Select Elements: Create dropdown menus with pre-defined options for users.

Example

function FeedbackForm() {
  const [name, setName] = useState("");
  const [feedback, setFeedback] = useState("");
  const [likesPizza, setLikesPizza] = useState(false);

  return (
    <form>
      <label>
        Name:
        <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
      </label>
      <label>
        Feedback:
        <textarea value={feedback} onChange={(e) => setFeedback(e.target.value)} />
      </label>
      <label>
        Do you like pizza?
        <input type="checkbox" checked={likesPizza} onChange={(e) => setLikesPizza(e.target.checked)} />
      </label>
    </form>
  );
}

Explanation

  • State variables are created for name (text input), feedback (text area), and likesPizza (checkbox).
  • Each input element uses the corresponding state variable for its value and an onChange handler to update the state based on user interaction.

Form Submission (onSubmit)

An important aspect of React forms is handling form submissions. This refers to the action when a user clicks a submit button or triggers a similar event within the form. By effectively utilizing the onSubmit event handler, you can capture the submitted form data and perform actions like sending it to a server, processing it within your application, or triggering further functionalities.

Example

function LoginForm(props) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault(); // Prevent default form submission behavior
    console.log("Form submitted! Username:", username, "Password:", password);
    // Simulate data submission (replace with your logic)
    props.onLogin(username, password); // Pass data to parent component (optional)
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
      </label>
      <label>
        Password:
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      </label>
      <button type="submit">Login</button>
    </form>
  );
}

Explanation

  • const [username, setUsername] = useState(""); and similar lines: Create state variables to store user input for username and password.
  • <form onSubmit={handleSubmit}>: The form element binds the onSubmit event handler to the handleSubmit function.
  • handleSubmit: This function is triggered when the form is submitted:
    • event.preventDefault(): Prevents the default browser behavior of reloading the page on form submission.
    • It logs the submitted username and password to the console.
    • You can replace this with logic to send data to a server or perform other actions (commented line).
    • Optionally, it can pass the submitted data (username and password) to a parent component using props (props.onLogin).

Managing Form Data with React State

In React forms, managing form data revolves around controlled components. Using the useState hook, you create state variables to store the values of each form element. These values are then directly bound to the corresponding input elements. The onChange event handler plays an important role in updating these state variables whenever the user interacts with the form elements, like typing in a text field or selecting a checkbox. This ensures that the React component’s state—and consequently, the displayed form values—always reflect the latest user input, providing a predictable and controllable way to manage forms in your React applications.

Creating Controlled Components (with useState)

In controlled components, the form element’s value is always controlled by the React component’s state. This ensures a clear link between user input and the underlying data, making it easier to handle form submissions and data validation. The useState hook plays an important role in creating controlled components by allowing you to define state variables that store the current values of each form element.

Example of Controlled Component for a Username Input Using useState

function UsernameForm() {
  const [username, setUsername] = useState("");

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

  return (
    <form>
      <label>
        Username:
        <input type="text" value={username} onChange={handleChange} />
      </label>
    </form>
  );
}

Explanation

  • const [username, setUsername] = useState("");: This line creates a state variable username using useState to store the entered username, initially set to an empty string.
  • <input type="text" value={username} onChange={handleChange} />:
    • Creates a text input field.
    • The value attribute is set to the username state variable, ensuring the displayed username reflects the current state.
    • The onChange event handler (handleChange) is attached to capture changes in the input field.
  • handleChange: This function updates the username state variable with the new value entered by the user (event.target.value).

Handling Form Element Changes (onChange)

In React forms, the onChange event handler plays an important part in capturing user input and keeping your form data synchronized. This event is triggered whenever a user interacts with a form element, such as typing in a text field, selecting a checkbox, or choosing an option from a dropdown menu. By effectively utilizing onChange, you can track these changes and update the corresponding state variables within your React component. This ensures that the form data you’re working with always reflects the latest user input.

Example

function UsernameForm() {
  const [username, setUsername] = useState("");

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

  return (
    <form>
      <label>
        Username:
        <input type="text" value={username} onChange={handleChange} />
      </label>
    </form>
  );
}

Explanation

  • const [username, setUsername] = useState("");: Creates a state variable username to store the username (initially empty).
  • <input type="text" value={username} onChange={handleChange} />:
    • Sets the input field’s value to the username state variable, displaying the current username.
    • Attaches the onChange event handler to the input field.
  • handleChange: This function is triggered whenever the user types in the input field:
    • It retrieves the new value from the input field using event.target.value.
    • It updates the username state variable with this new value using setUsername.

Basic Validation Techniques in React Forms

Validating form data is essential for maintaining data integrity and improving the user experience in React applications. Basic validation techniques help ensure users enter the correct information in the required format. Here, we’ll focus on the common validation techniques:

  1. Identify Required Fields: Determine the fields users must fill out before submitting the form. These fields can be marked with an asterisk (*) or a clear label indicating they are mandatory.
  2. Implement Basic Checks: Utilize conditional statements and state variables to check if required fields are empty. If a required field is empty, set an error message state variable and display an appropriate error message near the corresponding input field.
  3. Validate Email Addresses: Regular expressions (regex) provide a powerful tool for validating email formats. Here’s an example regex for basic email validation:
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

This regex checks for a valid username format (alphanumeric characters and some special symbols), followed by an “@” symbol, and then a domain name with subdomains (alphanumeric characters, hyphens, and periods). Use the test method on the regex object in your code to check if the email you entered matches the pattern.

  1. Validate Numbers: Similar to email validation, you can use regex to validate numbers. Here’s an example for validating phone numbers (US format):
const phoneRegex = /^\(?([0-9]{3})\)?[-. ]([0-9]{3})[-. ]([0-9]{4})$/;

This regex checks for a three-digit area code (optional parentheses) followed by a hyphen, period, or space. Another three-digit prefix, followed by another hyphen, period, or space, and finally, a four-digit local number.

  1. Display Error Messages: When validation fails, update a state variable to store the error message (e.g., “Invalid email format” or “Phone number must be in xxx-xxx-xxxx format”). Conditionally render the error message near the corresponding input field, typically using a styled paragraph element with an appropriate error class.
import React, { useState } from 'react';

function RegistrationForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [phone, setPhone] = useState("");
  const [errorMessage, setErrorMessage] = useState(null);

  const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  const phoneRegex = /^\(?([0-9]{3})\)?[-. ]([0-9]{3})[-. ]([0-9]{4})$/;

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

    // Update the corresponding state based on input field's name
    switch (name) {
      case "name":
        setName(value);
        break;
      case "email":
        setEmail(value);
        break;
      case "phone":
        setPhone(value);
        break;
      default:
        break;
    }

    // Clear previous error message
    setErrorMessage(null);
  };

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

    // Validation checks
    if (!name) {
      setErrorMessage("Name is required!");
      return;
    } else if (!emailRegex.test(email)) {
      setErrorMessage("Invalid email format!");
      return;
    } else if (!phoneRegex.test(phone)) {
      setErrorMessage("Invalid phone number format! Please use xxx-xxx-xxxx format.");
      return;
    }

    // Form submission logic (replace with your implementation)
    console.log("Form submitted! Name:", name, "Email:", email, "Phone:", phone);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name: (required)
        <input type="text" value={name} onChange={handleChange} name="name" />
      </label>
      <label>
        Email: (required)
        <input type="email" value={email} onChange={handleChange} name="email" />
      </label>
      <label>
        Phone:
        <input type="tel" value={phone} onChange={handleChange} name="phone" />
      </label>
      {errorMessage && <p className="error-message">{errorMessage}</p>}
      <button type="submit">Register</button>
    </form>
  );
}

export default RegistrationForm; 

Explanation

This React code implements a registration form with basic validation for required fields, email format, and phone number format.

  • State Variables: It uses useState to manage the state of the form fields (name, email, phone) and error messages.
  • Regular Expressions: It defines regular expressions for validating email and phone number formats.
  • handleChange Function: This function updates the corresponding state variable based on the changed input field and clears any previous error message.
  • handleSubmit Function: This function prevents default form submission, performs validation checks, and displays an error message if validation fails. If validation passes, it logs the submitted data (replace with your actual submission logic).
  • JSX Structure: The JSX code defines the form layout with labels, input fields, an error message paragraph (conditionally displayed), and a submit button.
  • Error Handling: If validation fails, the errorMessage state is set with a specific message depending on the failed check.
  • Form Submission: Clicking the “Register” button triggers the handleSubmit function for form processing.

The above code ensures that users fill out required fields, enter emails in a valid format, and provide phone numbers in the specified format (xxx-xxx-xxxx) before submitting the registration form.


Displaying Validation Errors in React Forms

In React forms, providing clear feedback to the user about validation errors is important. This helps users identify and fix mistakes before submitting the form. Here’s a general approach:

  1. Track Errors: Utilize state variables to store error messages associated with each form field. Update these state variables when validation fails for a specific field.
  2. Conditional Rendering: Conditionally render error messages based on the corresponding error state variable. This ensures that error messages only appear when validation fails for a particular field.
  3. Styling: Apply appropriate styles to the error messages to make them visually distinct and easy for the user to notice.

Example

function LoginForm() {
  const [username, setUsername] = useState("");
  const [errorMessage, setErrorMessage] = useState(null);

  const handleChange = (event) => {
    setUsername(event.target.value);
    setErrorMessage(null); // Clear previous error message
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (!username) {
      setErrorMessage("Username is required!");
      return;
    }
    // Submit form data (replace with your logic)
    console.log("Form submitted! Username:", username);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" value={username} onChange={handleChange} />
      </label>
      {errorMessage && <p className="error-message">{errorMessage}</p>}
      <button type="submit">Login</button>
    </form>
  );
}

Explanation

  • const [errorMessage, setErrorMessage] = useState(null);: Creates a state variable to store the error message (initially null).
  • handleChange: Updates username state and clears any previous error message.
  • handleSubmit: Checks if username is empty. If empty, sets the errorMessage and stops further execution.
  • {errorMessage && <p className="error-message">{errorMessage}</p>}: Conditionally renders the error message if it exists (using a logical AND operator &&).

Complex Input Types in React Forms

React forms handle various types of user input beyond basic text fields. Here, we explore two categories:

  1. File Uploads: Allow users to select and upload files from their devices. This can be useful for scenarios like uploading profile pictures, submitting documents, or attaching images to a post.
  2. Custom Input Components: Build reusable components that encapsulate specific input functionalities. This can involve styling or adding custom behavior to existing input types (e.g., date pickers, color pickers, or custom radio button styles).

Example

function ImageUploadForm() {
  const [selectedImage, setSelectedImage] = useState(null);

  const handleChange = (event) => {
    const file = event.target.files[0];
    if (file && file.type.startsWith('image/')) {
      setSelectedImage(file);
    } else {
      setSelectedImage(null);
      alert("Please select an image file!");
    }
  };

  return (
    <form>
      <label>
        Upload Image:
        <input type="file" accept="image/*" onChange={handleChange} />
      </label>
    </form>
  );
}

Explanation

  • const [selectedImage, setSelectedImage] = useState(null);: Stores the selected image file (initially null).
  • handleChange:
    • Retrieves the selected file.
    • Checks if it’s a valid image file type (using startsWith).
    • Updates selectedImage state if valid, otherwise clears it and displays an alert.
  • Form structure allows users to select an image file.

Example Custom Input Components

function StarRating() {
  const [rating, setRating] = useState(0);

  const handleClick = (starValue) => {
    setRating(starValue);
  };

  return (
    <div className="star-rating">
      {[1, 2, 3, 4, 5].map((star) => (
        <button key={star} onClick={() => handleClick(star)} className={star <= rating ? 'star-on' : 'star-off'}>★</button>
      ))}
    </div>
  );
}

Explanation

  • const [rating, setRating] = useState(0);: Stores the current rating (initially 0).
  • handleClick: Updates the rating state with the clicked star value.
  • JSX code renders star buttons using a map function.
  • Button styling (star-on or star-off) is applied based on the current rating.

Resetting Form State in React Forms

Maintaining a clean form state is crucial for a smooth user experience in React applications. This allows users to start fresh after submitting a form or encountering errors. Here’s how to reset form state:

  1. State Management: Utilize the useState hook to manage form state variables.
  2. Reset Function: Create a function that updates the state variables to their initial values.
  3. Trigger Reset: Call the reset function on specific events, such as a “Clear Form” button click or successful form submission.

Example

function ContactForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const resetForm = () => {
    setName("");
    setEmail("");
  };

  return (
    <form>
      <label htmlFor="name">
        Name:
        <input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} />
      </label>
      <label htmlFor="email">
        Email:
        <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      </label>
      <button type="submit">Submit</button>
      <button type="button" onClick={resetForm}>Clear Form</button>
    </form>
  );
}

Explanation

  • useState hook is used for name and email state variables.
  • resetForm function sets both state variables back to empty strings.
  • A “Clear Form” button is added with an onClick handler that calls the resetForm function.

Security Best Practices for React Forms

Ensuring the security of user data submitted through React forms is critical. Here’s a breakdown of key concerns:

  1. Sensitive Data: Carefully handle sensitive information like passwords or credit card details. Implement proper encryption during transmission and storage.
  2. XSS Attacks: Malicious users might inject scripts into form data to compromise user sessions or steal data. Sanitize user input to prevent these Cross-Site Scripting (XSS) vulnerabilities.
  3. Server-Side Validation: Don’t solely rely on client-side validation in React forms. Always implement robust validation and sanitization on the server-side to prevent manipulation of submitted data.

Example

import { useSanitize } from 'react-html-parser'; // Example library

function RegistrationForm() {
  const [username, setUsername] = useState("");

  const sanitizedUsername = useSanitize(username); // Sanitize user input

  const handleSubmit = async (event) => {
    event.preventDefault();
    const data = { username: sanitizedUsername }; // Use sanitized data

    try {
      const response = await fetch('/api/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error("Registration failed!");
      }

      console.log("Registration successful!");
    } catch (error) {
      console.error("Error:", error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="username">
        Username:
        <input type="text" id="username" value={username} onChange={(e) => setUsername(e.target.value)} />
      </label>
      <button type="submit">Register</button>
    </form>
  );
}

Explanation

  • We import a library like react-html-parser for sanitization.
  • useSanitize is used to sanitize the username input before storing it in the sanitizedUsername variable.
  • The handleSubmit function uses the sanitized data (sanitizedUsername) when constructing the data object to be submitted.
  • The code includes basic error handling for the API call (replace with your actual logic).

React Hook Form (RHF)

Managing complex forms in React can be a big challenge. Here’s where React Hook Form comes in. It’s a popular library that provides a set of hooks to streamline form handling and validation. Let’s look at the breakdown:

  1. Installation: Install the react-hook-form library using your preferred package manager.
  2. Import Hooks: Import the necessary hooks (useForm for basic form management and potentially others like yupResolver for advanced validation) from the library.
  3. Form State and Validation: Utilize the useForm hook to create state variables for form data and potentially define validation rules using libraries like Yup.
  4. Register Fields: Register each form element (input fields, select boxes, etc.) with the useForm hook to track their values and enable validation.
  5. Handle Form Submission: Implement a handleSubmit function to process form data after successful validation.

Example

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

function RegistrationForm() {
  const [name, setName] = useState(""); // Optional state for managing name separately (if needed)

  // Define validation schema using Yup
  const validationSchema = yup.object({
    name: yup.string().required("Name is required!"),
    email: yup.string().email("Invalid email format").required("Email is required!"),
    phone: yup.string().matches(/^\(?([0-9]{3})\)?[-. ]([0-9]{3})[-. ]([0-9]{4})$/, "Invalid phone number format! Please use xxx-xxx-xxxx"),
    password: yup.string().min(8, "Password must be at least 8 characters").required("Password is required!"),
  });

  // Use useForm hook with yupResolver for validation
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({ resolver: yupResolver(validationSchema) });

  const onSubmit = (data) => {
    console.log("Form data:", data);
    // Submit form data to your backend (replace with your logic)
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="name">
        Name: (required)
        <input type="text" {...register("name")} value={name} onChange={(e) => setName(e.target.value)} />
        {errors.name && <p className="error-message">{errors.name.message}</p>}
      </label>
      <label htmlFor="email">
        Email: (required)
        <input type="email" {...register("email")} />
        {errors.email && <p className="error-message">{errors.email.message}</p>}
      </label>
      <label htmlFor="phone">
        Phone:
        <input type="tel" {...register("phone")} />
        {errors.phone && <p className="error-message">{errors.phone.message}</p>}
      </label>
      <label htmlFor="password">
        Password: (required)
        <input type="password" {...register("password")} />
        {errors.password && <p className="error-message">{errors.password.message}</p>}
      </label>
      <button type="submit">Register</button>
    </form>
  );
}

export default RegistrationForm;

Explanation

  1. Imports:
    • useForm is imported from react-hook-form.
    • yupResolver and yup are imported for validation schema definition.
  2. State Management:
    • We’ve included an optional state variable name in case you want to manage the name input separately from the form state managed by React Hook Form.
  3. Validation Schema:
    • A validationSchema object is defined using Yup. It defines validation rules for each form field.
    • The schema includes checks for required fields, email format, phone number format (using regular expressions), and minimum password length.
  4. useForm Hook:
    • The useForm hook is used with the yupResolver configured to use the defined validation schema. This enables automatic validation based on the defined rules.
  5. Form State and Error Handling:
    • We destructure the formState object from useForm to access validation errors (errors).
    • The errors object now contains specific error messages for each field if validation fails.
  6. handleSubmit Function:
    • This function remains the same, handling form submission and logging form data.
  7. JSX Structure:
    • Each form field uses the register function with the corresponding field name to register it with the form.
    • We’ve included the optional value and onChange props for the name input if you decide to manage it separately.
    • Conditional rendering displays error messages using the errors object from the form state and the field names.
  8. Error Messages:
    • Error messages are now more specific, displaying the message defined in the Yup validation schema for each field.

Conclusion

React forms are powerful tools for collecting user input but require careful management for the best results. Prioritize controlled components for predictability, user-friendly validation with clear error messages, and accessibility for everyone. Always be careful of security when handling forms, especially with sensitive user data. By keeping these essentials in mind, you’ll build efficient and enjoyable forms for your users.


React Reference

React Forms