React Controlled Component

I’ve found that managing form data is one of the most frequent tasks you’ll face.

In the early days, I struggled with keeping the UI in sync with the underlying data, but then I mastered controlled components.

If you have ever wondered how to handle user input efficiently in a React application, you are in the right place.

In this guide, I will show you exactly what a controlled component is and how to implement it using real-world scenarios.

What is a React Controlled Component?

A controlled component is a component where the form data is handled by the React state.

Instead of the DOM holding the form data, the React component stays in charge of the input’s value.

I like to think of it as a “single source of truth” where the state and the input value are always perfectly synced.

Whenever the user types something, an event handler updates the state, and the input re-renders to show the new value.

Method 1: Handle a Simple Text Input

Let’s start with a basic example. Imagine we are building a “Zip Code Locator” for a logistics company based in Chicago.

We want to capture the user’s zip code to provide local shipping rates.

Here is the full code to implement a controlled text input:

import React, { useState } from 'react';

const ZipCodeScanner = () => {
  // We initialize the state to hold the zip code
  const [zipCode, setZipCode] = useState('');

  const handleInputChange = (event) => {
    const value = event.target.value;
    
    // In a real USA-based app, we only want numbers and max 5 digits
    if (/^\d{0,5}$/.test(value)) {
      setZipCode(value);
    }
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Searching shipping rates for Zip Code: ${zipCode}`);
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial' }}>
      <h2>USA Logistics - Zip Code Locator</h2>
      <form onSubmit={handleSubmit}>
        <label htmlFor="zip">Enter Zip Code: </label>
        <input
          type="text"
          id="zip"
          value={zipCode}
          onChange={handleInputChange}
          placeholder="e.g. 60601"
        />
        <button type="submit" style={{ marginLeft: '10px' }}>
          Check Rates
        </button>
      </form>
      <p>Current Input Value: {zipCode}</p>
    </div>
  );
};

export default ZipCodeScanner;

I executed the above example code and added the screenshot below.

React Controlled Component

In this example, the value prop of the input is tied to zipCode. Every time I type, handleInputChange updates the state, which then pushes the value back into the input.

Method 2: Work with Select Dropdowns

Dropdowns are a bit different, but the logic remains the same in a controlled environment. Suppose we are building a tax filing assistant where users need to select their US State.

Using a controlled selection ensures that the application knows exactly which state is selected at all times.

Here is how I usually structure a controlled select component:

import React, { useState } from 'react';

const StateTaxSelector = () => {
  const [selectedState, setSelectedState] = useState('CA');

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

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(`Calculating taxes for state: ${selectedState}`);
  };

  return (
    <div style={{ padding: '20px' }}>
      <h3>State Tax Filing Selection</h3>
      <form onSubmit={handleSubmit}>
        <label>Select your Residency: </label>
        <select value={selectedState} onChange={handleChange}>
          <option value="CA">California</option>
          <option value="NY">New York</option>
          <option value="TX">Texas</option>
          <option value="FL">Florida</option>
          <option value="IL">Illinois</option>
        </select>
        <button type="submit" style={{ marginLeft: '10px' }}>
          Proceed
        </button>
      </form>
      <div style={{ marginTop: '10px', color: 'blue' }}>
        You have selected: {selectedState}
      </div>
    </div>
  );
};

export default StateTaxSelector;

I executed the above example code and added the screenshot below.

Controlled Component React

I’ve found that setting an initial value in useState (like ‘CA’) is vital for a smooth user experience.

This prevents the component from being “uncontrolled” during the very first render.

Method 3: Manage Multiple Inputs with a Single State Object

When building a large form, like a US Mortgage Application, creating ten different useState hooks is inefficient.

I prefer using a single object to manage the state of the entire form. This makes the code cleaner and the logic much easier to scale as the project grows.

Check out this implementation for a contact form:

import React, { useState } from 'react';

const MortgageLeadForm = () => {
  const [formData, setFormData] = useState({
    firstName: '',
    lastName: '',
    annualIncome: '',
    employmentStatus: 'Employed'
  });

  const handleUpdate = (event) => {
    const { name, value } = event.target;
    
    // We use the spread operator to keep existing data
    setFormData((prevState) => ({
      ...prevState,
      [name]: value
    }));
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', border: '1px solid #ccc' }}>
      <h2>Pre-Qualification Form</h2>
      <div style={{ marginBottom: '10px' }}>
        <input
          name="firstName"
          placeholder="First Name"
          value={formData.firstName}
          onChange={handleUpdate}
          style={{ width: '100%', marginBottom: '5px' }}
        />
      </div>
      <div style={{ marginBottom: '10px' }}>
        <input
          name="lastName"
          placeholder="Last Name"
          value={formData.lastName}
          onChange={handleUpdate}
          style={{ width: '100%', marginBottom: '5px' }}
        />
      </div>
      <div style={{ marginBottom: '10px' }}>
        <input
          name="annualIncome"
          type="number"
          placeholder="Annual Income (USD)"
          value={formData.annualIncome}
          onChange={handleUpdate}
          style={{ width: '100%', marginBottom: '5px' }}
        />
      </div>
      <div>
        <label>Employment: </label>
        <select name="employmentStatus" value={formData.employmentStatus} onChange={handleUpdate}>
          <option value="Employed">Employed</option>
          <option value="Self-Employed">Self-Employed</option>
          <option value="Retired">Retired</option>
        </select>
      </div>
      <div style={{ marginTop: '20px', backgroundColor: '#f9f9f9', padding: '10px' }}>
        <strong>Form Preview:</strong>
        <p>Name: {formData.firstName} {formData.lastName}</p>
        <p>Income: ${formData.annualIncome}</p>
        <p>Status: {formData.employmentStatus}</p>
      </div>
    </div>
  );
};

export default MortgageLeadForm;

I executed the above example code and added the screenshot below.

Controlled Component in React

This pattern is my “go-to” for professional React development because it leverages the name attribute of the HTML tags.

Method 4: Handle Checkboxes (Boolean State)

Checkboxes in React can be tricky because they use the checked property instead of the value property.

I remember spending hours debugging this when I first started using React.

Let’s look at a “Terms of Service” agreement for a US-based Fintech app.

import React, { useState } from 'react';

const ComplianceForm = () => {
  const [isAgreed, setIsAgreed] = useState(false);

  const handleToggle = (event) => {
    // Note: We use event.target.checked here
    setIsAgreed(event.target.checked);
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>Federal Regulation Disclosure</h2>
      <p style={{ fontSize: '12px', color: '#666' }}>
        By checking the box below, you acknowledge that you have read the 
        standard banking disclosures and agree to the terms.
      </p>
      <label>
        <input
          type="checkbox"
          checked={isAgreed}
          onChange={handleToggle}
        />
        I agree to the Terms of Service
      </label>
      <div style={{ marginTop: '20px' }}>
        <button disabled={!isAgreed} style={{ padding: '10px 20px', cursor: isAgreed ? 'pointer' : 'not-allowed' }}>
          Submit Application
        </button>
      </div>
    </div>
  );
};

export default ComplianceForm;

Using a controlled checkbox allows you to easily disable the submit button until the user complies with the terms.

Controlled vs Uncontrolled Components

In the React ecosystem, you will also hear about “uncontrolled components.”

Uncontrolled components store their state in the DOM, and you access them using refs.

While uncontrolled components are sometimes faster to write for very simple scripts, I almost always recommend controlled components.

Controlled components make it easier to debug, test, and implement complex UI patterns like “instant search” or “live validation.”

Pros and Cons of Controlled Components

Pros:

  • Predictability: The UI is always a reflection of the state.
  • Validation: You can prevent invalid characters (like non-numeric zip codes) as the user types.
  • Dynamic UI: You can easily change other parts of the page based on what the user is typing.

Cons:

  • Boilerplate: You have to write an event handler for every input (unless you use the object pattern).
  • Performance: In very large forms with hundreds of inputs, frequent re-renders might cause slight lag (though this is rare).

In this tutorial, I’ve covered several ways to use React controlled components to manage form data.

I’ve used these exact patterns in production environments for years, and they have never failed me.

Whether you are building a simple login screen or a complex financial dashboard, keeping your data in the state is the way to go.

You may also like to read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.