Type to generate UI components from text

OR

Browse thousands of MUI, Tailwind, React components that are fully customizable and responsive.

Explore Components

How to Handle React Forms: A Comprehensive Guide

Forms are a crucial component of web applications, enabling users to interact with and submit data. In React, handling forms involves various techniques, such as controlled and uncontrolled forms, form validation, and utilizing popular form libraries like React Hook Form. This comprehensive guide will walk you through these techniques, providing detailed explanations and code examples to help you understand and implement effective form handling in your React applications.

Here’s a quick React Form tutorial to start.

Prerequisite

You need a basic understanding of React to follow along with this article.

Controlled React Form

Controlled components in React maintain their state in the component’s state, and their values are controlled by React. For a controlled form, you link the input values to the component’s state and update the state through event handlers. This ensures that React has complete control over the form elements.

Below is a code example of a controlled form component:

import React, { useState } from 'react';

const ControlledForm = () => {
  // state for each input field
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleUsernameChange = (e) => {
    setUsername(e.target.value);
  };

  const handlePasswordChange = (e) => {
    setPassword(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // Process form data
    console.log('Username:', username);
    console.log('Password:', password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          value={username}
          onChange={handleUsernameChange}
        />
      </label>
      <label>
        Password:
        <input
          type="password"
          value={password}
          onChange={handlePasswordChange}
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default ControlledForm;

In this example, each input field (username and password) has its own associated state variable, and the respective onChange handlers (handleUsernameChange and handlePasswordChange) update the specific state when the input values change.

This approach provides a clear separation of concerns for each input field, making it easier to manage and update the state for each specific form element. But in most cases, you may want to manage the form elements in one state object. Here’s how to do so:

How to Handle Multiple Form Fields

When dealing with forms containing multiple fields, you can use the controlled approach to manage each field’s state individually. This makes it easier to track and validate the input data, providing a structured and predictable way to handle complex forms.

// ControlledFormWithMultipleFields.js

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

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // Process form data
    console.log(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        First Name:
        <input
          type="text"
          name="firstName"
          value={formData.firstName}
          onChange={handleInputChange}
        />
      </label>
      <label>
        Last Name:
        <input
          type="text"
          name="lastName"
          value={formData.lastName}
          onChange={handleInputChange}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleInputChange}
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default ControlledFormWithMultipleFields;

Explanation:

This component demonstrates the concept of controlled forms in React, where the state of the form is managed by React, and changes to the form fields are controlled through event handlers. The state is updated in a way that preserves the immutability of the data by using the spread operator (…) to create a new object with the updated values.

Advantages of Controlled Forms:

  • Predictable State: The form state is managed by React, making it predictable and easy to understand. The state is explicitly defined and controlled by React components.

  • Easier Validation: Validation becomes more straightforward because you have direct access to the form state. You can implement validation logic within the event handlers, providing immediate feedback to users.

  • Dynamic Updates: React components can dynamically update the form values based on external factors, such as data from an API or changes in other parts of the application. This allows for dynamic and real-time updates.

  • Consistent Rendering: Since the form state is controlled by React, rendering becomes consistent and reliable. React ensures that the UI reflects the current state of the application.

  • Integration with React Ecosystem: Controlled forms seamlessly integrate with other React features and libraries, such as state management with hooks, context API, and component lifecycle methods.

Disadvantages of Controlled Forms:

  • Boilerplate Code: Controlled forms often involve writing more boilerplate code compared to uncontrolled forms. Each form field requires its state variable and associated event handlers, potentially leading to increased verbosity.

  • Performance Impact:

    In large forms with many controlled components, the performance might be impacted due to the constant re-rendering caused by state updates. Techniques like debouncing may be needed to mitigate this.

  • Verbosity in JSX: The JSX code can become verbose, especially when dealing with forms containing numerous fields. This can make the code harder to read and maintain.

  • Complex Forms: In certain scenarios, dealing with complex forms, especially those with dynamic fields or conditional rendering, might require additional effort. Managing the state for every dynamic aspect can become challenging.

  • Learning Curve: For developers new to React, the concept of controlled forms and managing state might pose a learning curve. Uncontrolled forms may seem simpler for beginners in some cases.

  • Read-Only Fields: Handling read-only fields or fields dependent on external libraries (e.g., date pickers, autocomplete) may be more complex in controlled forms, requiring additional considerations.

Considerations:

When choosing between controlled and uncontrolled forms, consider the specific requirements of your application. Controlled forms are generally recommended for most scenarios, but there might be cases where uncontrolled forms are more suitable, such as when integrating with non-React code or when dealing with large, dynamic forms where performance is a concern.

In practice, many React developers find controlled forms to be a powerful and flexible approach for managing form state while providing a predictable and controlled user experience. The disadvantages can often be mitigated with proper code organization, state management techniques, and leveraging React features effectively.

Uncontrolled React Form

Uncontrolled components, on the other hand, store their state within the DOM. Two approaches for uncontrolled forms are using useRef hook and the FormData API. This approach is useful when integrating with non-React code or when you want to leverage the existing HTML form behavior. Let’s explore both methods.

Using useRef

useRef is a React hook that provides a way to access and interact with the DOM directly. In the context of uncontrolled forms, `useRef` can be employed to obtain references to form elements and extract their values without managing state in the React component.

Code Example

// UncontrolledFormWithRef.js
import React, { useRef } from 'react';

const UncontrolledFormWithRef = () => {
  const usernameRef = useRef();
  const passwordRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    // Access form data using refs
    console.log('Username:', usernameRef.current.value);
    console.log('Password:', passwordRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" ref={usernameRef} />
      </label>
      <label>
        Password:
        <input type="password" ref={passwordRef} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default UncontrolledFormWithRef;

Explanation:

In this example, we use the useRef hook to create references (usernameRef and passwordRef) to the input fields. When the form is submitted, we access the form data directly from the DOM using these references.

Using Form Data

Another method for handling uncontrolled forms involves using the FormData API. This API allows you to construct a set of key/value pairs representing form fields and their values, which can be particularly helpful when dealing with large forms.

Code Example

// UncontrolledFormWithFormData.js
import React from 'react';

const UncontrolledFormWithFormData = () => {

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    // Access form data using FormData
    console.log('Username:', formData.get('username'));
    console.log('Password:', formData.get('password'));
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" name="username" />
      </label>
      <label>
        Password:
        <input type="password" name="password" />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default UncontrolledFormWithFormData;

Explanation:

From the example above, you can see that the fields username and password are captured with the FormData API on submission without explicitly managing state for the form fields. The handleSubmit function prevents the default form submission, creates a FormData object from the form (e.target), and logs the entered username and password to the console. The form consists of input fields for username and password, and it uses the onSubmit event to trigger the form submission logic.

When should you use useRef or FormData API

Here’s a concise table highlighting the advantages and disadvantages of using FormData API and useRef in a React application and when it’s appropriate to use either:

[Embed table here:

https://www.notion.so/react-forms-6c17180616614b5987e1cd8d6ae041aa?pvs=4#7e118462488140dabb2afd5c5252a04e

Advantages of Uncontrolled Forms:

  • Simplicity and Less Boilerplate: Uncontrolled forms typically involve less code and are less verbose compared to controlled forms. There is no need to set up and manage state for every form field.

  • Direct DOM Access: ref can be used to directly access and manipulate the DOM elements, providing a more imperative and straightforward approach, especially for integrating with non-React code or external libraries.

  • Performance Benefits: Uncontrolled forms can be more performant, especially in scenarios where there are frequent updates to the form, as changes to form elements do not trigger React re-renders.

  • Integration with Non-React Code: Well-suited for scenarios where you need to integrate with non-React code or when dealing with external libraries that expect direct DOM references.

Disadvantages of Uncontrolled Forms:

  • Limited React Control: React has less control over the form state in uncontrolled forms, making it harder to implement certain features such as real-time validation or dynamic updates.

  • Validation Challenges: Implementing real-time validation and ensuring data consistency can be more challenging in uncontrolled forms, as there is no centralized state to manage.

  • Readability and Maintainability: Code may become less readable and harder to maintain, especially as the logic for form handling is scattered throughout the component rather than being centralized in the component’s state.

  • Consistency with React Paradigm: Uncontrolled forms may deviate from the typical React paradigm of controlled components, making it less consistent with the overall approach of managing state in React applications.

  • Limited Use of React Features: Uncontrolled forms might not take full advantage of certain React features like hooks, context API, or component lifecycle methods, which are commonly used in controlled forms.

Controlled vs. Uncontrolled Forms

Controlled forms offer a more declarative approach, making it easier to implement features like validation and conditional rendering. In contrast, uncontrolled forms provide a more imperative approach suitable for certain scenarios. The choice depends on the application’s requirements.

React Form with Validation

Form validation is crucial for ensuring that the data submitted meets specified criteria. There are various ways to add form validation in React, so in this guide, we’ll go

Uncontrolled React Form Validation

When using an uncontrolled form we can take advantage of the default validations provided by the browser. We can also make use of ref for additional validation logic. Below is a simplified example demonstrating how to perform form validation in an uncontrolled form using useRef hook:

import React, { useRef } from 'react';

const UncontrolledFormWithValidation = () => {
  const usernameRef = useRef();
  const passwordRef = useRef();

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

    // Access form field values using refs
    const username = usernameRef.current.value;
    const password = passwordRef.current.value;

    // Perform validation
    if (!username.trim()) {
      alert('Please enter a username.');
      return;
    }

    if (password.length < 6) {
      alert('Password must be at least 6 characters long.');
      return;
    }

    // Form data is valid, proceed with further actions
    console.log('Username:', username);
    console.log('Password:', password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" ref={usernameRef} />
      </label>
      <label>
        Password:
        <input type="password" ref={passwordRef} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default UncontrolledFormWithValidation;

Explanation

  • Refs for Form Fields:Refs (usernameRef and passwordRef) are created using useRef to directly access the values of the form fields.

  • Form Submission Handler: The handleSubmit function is called on form submission, preventing the default form behavior.

  • Validation Logic: Validation checks are implemented within the handleSubmit function. In this example, it checks if the username is not empty and if the password is at least 6 characters long. Validation errors are displayed using alert.

  • Alerts for Validation Errors: If validation fails, alerts are shown, and the form submission is halted. If the form data is valid, it proceeds with further actions.

We can also make use of the FormData API along with React state to check for validation errors. The example below is a simplified validation check using the FormData API.

import React, { useState } from 'react';

const UncontrolledFormWithValidationAndFormData = () => {
  const [usernameError, setUsernameError] = useState('');
  const [passwordError, setPasswordError] = useState('');

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

    // Create FormData object from the form
    const formData = new FormData(e.target);

    // Access form field values using FormData
    const username = formData.get('username');
    const password = formData.get('password');

    // Perform validation
    if (!username.trim()) {
      setUsernameError('Please enter a username.');
      setPasswordError('');
      return;
    }

    if (password.length < 6) {
      setUsernameError('');
      setPasswordError('Password must be at least 6 characters long.');
      return;
    }

    // Reset error messages
    setUsernameError('');
    setPasswordError('');

    // Form data is valid, proceed with further actions
    console.log('Username:', username);
    console.log('Password:', password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" name="username" />
        {usernameError && <span style={{ color: 'red' }}>{usernameError}</span>}
      </label>
      <label>
        Password:
        <input type="password" name="password" />
        {passwordError && <span style={{ color: 'red' }}>{passwordError}</span>}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default UncontrolledFormWithValidationAndFormData;

Explanation:

  • State for Error Messages: State variables (usernameError and passwordError) are created using useState to manage error messages for each form field.

  • Display Error Messages on UI: Within each label element, error messages are conditionally rendered using {usernameError && …} and {passwordError && …}. If there’s an error, the error message is displayed in red.

  • Reset Error Messages: Error messages are reset to an empty string () when the form data is valid, ensuring that the UI reflects the correct state.

Controlled React Form Validation

Controlled forms often provide a straightforward way to implement validation logic because it’s more declarative. The example below shows a simple validation where the fields must contain at least 6 characters.

// ControlledFormWithValidation.js

const ControlledFormWithValidation = () => {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
  });

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

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));

    // Validation logic
    setErrors((prevErrors) => ({
      ...prevErrors,
      [name]: value.length < 6 ? 'Must be at least 6 characters' : '',
    }));
  };

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

    // Check for errors before submitting
    if (Object.values(errors).every((error) => !error)) {
      // Process form data
      console.log(formData);
    } else {
      console.log('Form has errors. Please fix them.');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleInputChange}
        />
        {errors.username && <span className="error">{errors.username}</span>}
      </label>
      <label>
        Password:
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleInputChange}
        />
        {errors.password && <span className="error">{errors.password}</span>}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

export default ControlledFormWithValidation;

Explanation:

In this example, we extend the controlled form with validation logic. The errors state is used to track validation errors, and the handleInputChange function is updated to perform validation checks. The form is only submitted if there are no errors.

Form Libraries

When dealing with complex forms in React, especially those requiring advanced validation, dynamic fields, and seamless integration with the state, picking a library is always a good next thing to do. There are two popular libraries for managing forms in React which are React Hook Form and Formik. In this guide, we’ll only go over React Hook Form since most of its concepts are similar to other libraries.

React Hook Form

React Hook Form is a lightweight and performant form library that leverages React Hooks for state management. In this section we’ll be building a basic restaurant form layout, with validation intergrated all powered by React Hook Form.

Here’s a preview of what we’ll be building.

import { useState } from 'react';
import { useForm } from 'react-hook-form';

function App() {
  const [isSubmitted, setIsSubmitted] = useState(false);
  const {
    reset,
    register,
    formState: { errors },
    handleSubmit,
  } = useForm();

  const ErrorMsg = ({ inputName }) => (
    <>
      {errors[inputName] && (
        <p className='text-sm text-red-400 font-medium block  px-4'>
          {errors[inputName]['message']
            ? errors[inputName]['message']
            : errors[inputName]['type'] === 'allowed'
            ? `invalid username`
            : `${inputName} is required`}
        </p>
      )}
    </>
  );

  const onSubmit = (data) => {
    setIsSubmitted(true);
    reset();
  };

  if (isSubmitted)
    return (
      <div className='container'>
        <div>
          <h4 className='text-3xl my-6 title'>Data Sent</h4>
          <button
            className='text-indigo-500'
            onClick={(e) => setIsSubmitted(false)}
          >
            Back to Form
          </button>
          <a
            href='https://react-hook-form.com/get-started'
            className='text-gray border-2 border-solid border-gray-600 px-6 py-1 rounded mt-3'
            target='_blank'
          >
            Visit React Hook Form
          </a>
        </div>
      </div>
    );

  return (
    <div className='container'>
      <form onSubmit={handleSubmit(onSubmit)}>
        <h1>Foodie Restaurant</h1>

        <div>
          <input
            {...register('username', {
              required: true,
            })}
            className={`!w-full`}
            placeholder='Username'
          />
          <ErrorMsg inputName='username' />
        </div>

        <div>
          <input
            className={`!w-full`}
            placeholder='Name'
            {...register('name', { required: true })}
          />
          <ErrorMsg inputName='name' />
        </div>

        <div>
          <input
            className={`!w-full`}
            type='email'
            placeholder='Email'
            {...register('email', { required: true })}
          />
          <ErrorMsg inputName='email' />
        </div>

        <div>
          <input
            className={`!w-full`}
            type='tel'
            placeholder='Phone'
            {...register('phone', {
              required: true,
              maxLength: { value: 13, message: 'Max length 13' },
            })}
          />
          <ErrorMsg inputName='phone' />
        </div>

        <div>
          <input
            className={`!w-full`}
            type='date'
            placeholder='Schedule'
            {...register('schedule', { required: true })}
          />
          <ErrorMsg inputName='schedule' />
        </div>

        <button>Submit</button>
      </form>
    </div>
  );
}

export default App;

Explanation:

Here’s the explanation of the code snippet above:

  1. State Management:

    const [isSubmitted, setIsSubmitted] = useState(false);

    The component uses the useState hook to manage the state of isSubmitted. This state is used to determine whether the form has been successfully submitted.

  2. Form Initialization:

    const { reset, register, formState: { errors }, handleSubmit, } = useForm();

    The useForm hook from react-hook-form is used to initialize the form. It provides methods such as reset, register, and handleSubmit.

    • reset is used to reset the form after submission.

    • register is used to register input fields and set up validation rules.

    • formState gives access to the form state, including errors.

  3. Error Message Component:

    const ErrorMsg = ({ inputName }) => (
      <>
        {errors[inputName] && (
          <p className='text-sm text-red-400 font-medium block  px-4'>
            {errors[inputName]['message']
              ? errors[inputName]['message']
              : errors[inputName]['type'] === 'allowed'
              ? `invalid username`
              : `${inputName} is required`}
          </p>
        )}
      </>
    );

    The ErrorMsg component is a reusable component for displaying error messages based on form validation errors. It checks the type of error and displays a corresponding message.

  4. Form Submission Handling:

    const onSubmit = (data) => {
      setIsSubmitted(true);
      reset();
    };

    The onSubmit function is triggered when the form is submitted. It sets isSubmitted to true and resets the form using reset().

  5. Conditional Rendering After Submission:

    if (isSubmitted)
      return (
        // JSX for the success message and a button to go back to the form
      );
    

    If the form is successfully submitted (isSubmitted is true), the component renders a success message and a button to go back to the form.

  6. Form JSX:

    return (
      // JSX for the form with input fields and a submit button
    );

    The main JSX renders the form with input fields for username, name, email, phone, and schedule, along with corresponding error messages.

    The form is conditionally rendered based on whether it has been submitted.

  7. Styling:

    The component uses Tailwind CSS classes for styling, and CSS but is not specified in the code snippet, since that’s not our focus.

Summarized Explanation:

In summary, the example code demonstrates a simple use case of react-hook-form to manage form state, perform validation, and handle form submission in a React application. The form includes various input fields, each with its own validation rules and error handling. The conditional rendering allows the display of a success message after the form is submitted. With form libraries, you can handle more complicated forms.

Here’s a GIF of the application:

Frequently Asked Questions (FAQs)

What are React forms?

React forms are components that allow users to input and submit data on a webpage. They are used for user interactions, such as login, registration, or data submission.

How do you add a form in React?

In React, you add a form by creating a component that includes HTML form elements, managing their state using React state, and handling form submission using event handlers.

What is the best form library for React?

There are several popular form libraries for React, with Formik and React Hook Form being commonly recommended for their ease of use and flexibility.

What is the best way to handle forms in React?

The best way to handle forms in React depends on the complexity of your application. Libraries like Formik or React Hook Form can simplify form management, while basic forms can be handled using React state and event handlers.

Why use useForm in React?

useForm is commonly associated with the React Hook Form library. It’s a hook that helps manage forms by handling form state and validation, making it convenient for React developers to streamline form-related logic.

How do I post a form in React?

Posting a form in React involves handling the form submission event, preventing the default behavior, gathering the form data from state, and then sending it to a server using techniques like AJAX requests or the Fetch API.

Final thoughts on React forms

In summary, managing forms in React requires thoughtful consideration of different techniques. Controlled forms provide predictability and real-time validation, suited for applications integrated with the React ecosystem. On the other hand, uncontrolled forms, using the FormData API and useRef, offer simplicity and performance benefits, ideal for dynamic forms and direct DOM manipulation.

The decision between these approaches depends on project requirements, and a strategic combination of both may be beneficial. Whether opting for the granular control of controlled forms or the simplicity of uncontrolled forms, finding the right balance enhances the overall user experience in your React applications.

Before you leave, have you heard about PureCode.ai? With PureCode.ai all your code development process can be made easier, by making use of ready-to-use components which will allow you to prioritize more important and thought-intensive tasks to speed up the development of your user interface. Try us out today!

Favourite Jome

Favourite Jome