React Forms

  • React forms allow users to interact with and submit data to the application, serving as the bridge between your users and your backend.
  • Unlike standard HTML forms, React typically uses a controlled component approach. This means React's state becomes the "single source of truth" for the form data, giving you full control over validation and submission.
Developer Tip: In traditional HTML forms, the DOM handles the form data. In React, we prefer to handle that data within the component's state to keep the UI and the data perfectly in sync.

Creating a Simple Form

In a controlled form, every time a user types a character, an event handler updates the state. The input's value is then set from that state. This circular flow ensures the input always displays exactly what is in your React state.

import React, { useState } from 'react';

const SimpleForm = () => {
  // We initialize the state as an empty string
  const [inputValue, setInputValue] = useState('');

  const handleChange = (e) => {
    // We update the state with the current value of the input
    setInputValue(e.target.value);
  };

  return (
    <form>
      <label>
        Enter text:
        <input 
          type="text" 
          value={inputValue} 
          onChange={handleChange} 
        />
      </label>
      <p>You entered: {inputValue}</p>
    </form>
  );
};
Common Mistake: Forgetting to provide an onChange handler while providing a value prop. This will result in a read-only input field that the user cannot type into!

Handling Form Submission

When a user submits a form (by clicking a button or pressing Enter), the browser's default behavior is to refresh the page. In a Single Page Application (SPA), we want to prevent this refresh so we can handle the data with JavaScript.

import React, { useState } from 'react';

const SubmissionForm = () => {
  const [inputValue, setInputValue] = useState('');

  const handleSubmit = (e) => {
    // This is crucial: it stops the browser from reloading the page
    e.preventDefault();
    
    // Here you would typically send data to an API
    console.log("Processing data:", inputValue);
    alert(`Form submitted with value: ${inputValue}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Enter text:
        <input 
          type="text" 
          value={inputValue} 
          onChange={(e) => setInputValue(e.target.value)} 
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};
Best Practice: Always use the onSubmit event on the <form> element rather than an onClick on the button. This ensures the form submits correctly when a user presses the "Enter" key.

Handling Different Input Types

Not all inputs work the same way. While text inputs use e.target.value, checkboxes use the checked property. React handles these nuances easily by mapping specific state variables to the correct attributes.

import React, { useState } from 'react';

const InputTypesForm = () => {
  const [isChecked, setIsChecked] = useState(false);
  const [selectedOption, setSelectedOption] = useState('option1');

  return (
    <form>
      <label>
        <input
          type="checkbox"
          // For checkboxes, we use 'checked' instead of 'value'
          checked={isChecked}
          onChange={() => setIsChecked(!isChecked)}
        />
        Accept Terms and Conditions
      </label>
      <br />
      <p>Select your preference:</p>
      <label>
        <input
          type="radio"
          value="option1"
          checked={selectedOption === 'option1'}
          onChange={() => setSelectedOption('option1')}
        />
        Option 1
      </label>
      <label>
        <input
          type="radio"
          value="option2"
          checked={selectedOption === 'option2'}
          onChange={() => setSelectedOption('option2')}
        />
        Option 2
      </label>
    </form>
  );
};
Watch Out: Radio buttons in a group must share the same name attribute if you want them to behave like a standard group where only one can be selected, although React state management often makes the name attribute optional for functionality.

Using Controlled Components

In real-world applications, you'll often have many inputs. Instead of creating a dozen different state variables, you can use a single object to manage the entire form state. This makes your code cleaner and easier to scale.

import React, { useState } from 'react';

const ControlledComponentsForm = () => {
  // Managing multiple fields in one state object
  const [formData, setFormData] = useState({
    username: '',
    password: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    // Use the name attribute to update the correct piece of state
    setFormData({
      ...formData,
      [name]: value
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Username: ${formData.username}, Password: ${formData.password}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input
          type="text"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </label>
      <br />
      <label>
        Password:
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </label>
      <br />
      <button type="submit">Login</button>
    </form>
  );
};
Developer Tip: When using an object for state, always remember to spread the existing state (...formData) before updating a specific field, otherwise you will delete the other fields in your object!

 

Summary

Working with forms in React involves a shift in mindset: the component state drives what the user sees, and user actions drive updates to that state. This "controlled" approach provides a robust framework for implementing features like real-time validation, conditional formatting, and complex multi-step forms. While manual state management works great for small forms, for massive enterprise forms, developers often look into libraries like Formik or React Hook Form to reduce boilerplate code.