⚛️ React Q46 / 113

How to handle forms in React?

AI-Powered Answer ✓ Answered

Forms are fundamental for user interaction in web applications, allowing users to input data. React provides powerful ways to manage form state and user input, primarily through the concept of controlled components.

Controlled Components: The React Way

In HTML, form elements like <input>, <textarea>, and <select> typically maintain their own state internally. When handling forms in React, you generally want this state to be managed by the React component itself. This is achieved through "controlled components," where React controls the value of the input element.

A controlled component gets its current value via props and notifies changes via callbacks like onChange. The parent component re-renders the input with the new value, making the component "controlled" by React state.

Basic Controlled Input

To make an input controlled, you bind its value prop to a piece of state and update that state using an onChange event handler.

javascript
import React, { useState } from 'react';

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

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

  const handleSubmit = (event) => {
    alert('A name was submitted: ' + name);
    event.preventDefault(); // Prevents default browser refresh
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

In this example, name is the state variable holding the input's current value. The onChange handler handleChange updates this state every time the input value changes. The handleSubmit function is called when the form is submitted, allowing you to process the form data. event.preventDefault() is crucial to stop the browser from reloading the page.

Handling Multiple Inputs

When dealing with multiple controlled inputs, you can manage them all with a single state object. You'll use the name attribute of the input elements and computed property names in your handleChange function to update the correct piece of state.

javascript
import React, { useState } from 'react';

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

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

  const handleSubmit = (event) => {
    alert('Form Data: ' + JSON.stringify(formData));
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Email:
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </label>
      <br />
      <button type="submit">Submit</button>
    </form>
  );
}

Other Controlled Input Types

<textarea> and <select> elements are also controlled similarly to <input>. For checkbox and radio inputs, you'll typically use the checked prop instead of value.

javascript
import React, { useState } from 'react';

function EssayForm() {
  const [essay, setEssay] = useState('Please write an essay about your favorite DOM element.');

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

  return (
    <form>
      <label>
        Essay:
        <textarea value={essay} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}
javascript
import React, { useState } from 'react';

function FlavorForm() {
  const [flavor, setFlavor] = useState('coconut');

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

  return (
    <form>
      <label>
        Pick your favorite flavor:
        <select value={flavor} onChange={handleChange}>
          <option value="grapefruit">Grapefruit</option>
          <option value="lime">Lime</option>
          <option value="coconut">Coconut</option>
          <option value="mango">Mango</option>
        </select>
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}
javascript
import React, { useState } from 'react';

function CheckboxForm() {
  const [isGoing, setIsGoing] = useState(true);

  const handleInputChange = (event) => {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    setIsGoing(value);
  };

  return (
    <form>
      <label>
        Is going:
        <input
          name="isGoing"
          type="checkbox"
          checked={isGoing}
          onChange={handleInputChange}
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

Form Validation

Client-side validation is crucial for user experience. In React, you can integrate validation logic directly into your onChange or onSubmit handlers, managing validation messages through component state.

javascript
import React, { useState } from 'react';

function ValidatedForm() {
  const [username, setUsername] = useState('');
  const [error, setError] = useState('');

  const handleChange = (event) => {
    const newUsername = event.target.value;
    setUsername(newUsername);
    if (newUsername.length < 3) {
      setError('Username must be at least 3 characters long.');
    } else {
      setError('');
    }
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (error) {
      alert('Please fix the errors before submitting.');
      return;
    }
    alert('Form submitted with username: ' + username);
    // Process form data
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" value={username} onChange={handleChange} />
      </label>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

Uncontrolled Components

While controlled components are generally preferred, "uncontrolled components" let the DOM handle the form data. You use a ref to get form values directly from the DOM when needed, typically during submission. This approach can be simpler for very basic forms or for integrating with non-React code, but it provides less control over input state. File inputs are often uncontrolled as their value is read-only for security reasons and needs to be accessed via a ref.

javascript
import React, { useRef } from 'react';

function UncontrolledForm() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    alert('A name was submitted: ' + inputRef.current.value);
    event.preventDefault();
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={inputRef} defaultValue="Guest" />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

Form Libraries

For complex forms with extensive validation, nested data, asynchronous submission, and performance optimizations, dedicated form libraries can significantly simplify your code and improve developer experience.

  • Formik: A popular library that helps with managing form state, validation, and submission without getting in your way. It provides utilities like useFormik hook, Formik component, and various render props.
  • React Hook Form: Focuses on performance and developer experience by leveraging uncontrolled components and native HTML form validation. It minimizes re-renders and provides a simple API based on hooks.
  • React Final Form: A subscription-based form state management library that is highly performant and flexible, providing granular control over re-renders.