Back to Blog
Lesson 28 of the Intermediate React: Hooks, State & Data Patterns course
ReactJune 26, 20263 min read

Controlled vs Uncontrolled Components: React Form Mastery

Master form handling in React by understanding the trade-offs between controlled components and uncontrolled components. Learn when to use state versus refs.

Reactformsstate managementhookscontrolled componentsjavascriptfrontend

Previously in this course, we covered Asynchronous Data Lifecycle to handle API states. Now that our dashboard displays live data, we need to allow users to interact with it via forms. Choosing between controlled components and uncontrolled components is the fundamental decision that dictates how your form state flows.

Understanding Form State from First Principles

In React, the "source of truth" for your data is almost always state. However, forms are a unique beast because the DOM itself maintains its own internal state for inputs.

Controlled components delegate that responsibility to React. You bind the value of an input to a piece of state and update it via an onChange handler. This makes the input "controlled" by React’s state machine.

Uncontrolled components let the DOM keep the state. You use a ref (as we practiced in Mastering useRef for DOM Access) to "pull" the value out of the input only when you actually need it, such as during a form submission.

When to Use Controlled Components

Use controlled components when you need real-time validation, dynamic UI updates based on input, or conditional disabling of buttons. If the input state needs to drive other parts of the application, it must be controlled.

JSX
import { useState } from CE9178">'react';

function ControlledInput() {
  const [email, setEmail] = useState(CE9178">'');

  return (
    <input 
      type="email" 
      value={email} 
      onChange={(e) => setEmail(e.target.value)} 
    />
  );
}

Because email is in our state, we can easily display an error message if the user types an invalid domain or disable a "Save" button if the field is empty.

When to Use Uncontrolled Components

Uncontrolled components are ideal for simple forms where you don't care about the input until the moment the user clicks "Submit." They are more performant because they don't trigger a re-render on every keystroke.

JSX
import { useRef } from CE9178">'react';

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

  const handleSubmit = () => {
    alert(CE9178">`Submitted value: ${inputRef.current.value}`);
  };

  return (
    <>
      <input type="text" ref={inputRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

Choosing the Right Tool: A Decision Framework

In our dashboard project, we are building a settings page. Here is how to decide:

  1. Controlled: Use for inputs requiring real-time feedback (e.g., "Username available" checks or character counters).
  2. Uncontrolled: Use for file inputs (which are inherently uncontrolled in browsers), one-off login fields, or large forms where re-rendering on every keystroke causes perceptible lag.

Hands-on Exercise: Refining the Dashboard Search

In our current dashboard, we have a search bar. Let's refactor it.

  1. If you want the search results to filter as the user types, convert the search input to a controlled component and sync it with your filter state.
  2. If you only want the search to trigger when the user clicks a "Search" button, use an uncontrolled component with a ref to grab the value only on the button click.

Try implementing the uncontrolled version first to see how much code you save, then switch to controlled to feel the difference in reactivity.

Common Pitfalls

  • Mixing Controlled and Uncontrolled: Never try to set both value and defaultValue on the same input. React will throw a warning because it doesn't know who owns the state.
  • Stale Refs: If you use an uncontrolled input, ensure you aren't trying to access ref.current during the initial render before the component has mounted.
  • Over-Controlling: Don't put every single input into global state if it doesn't need to be there. Local state is often sufficient and cleaner.

Recap

We’ve learned that controlled components provide maximum control via React state, while uncontrolled components offer a lightweight alternative using useRef. By selecting the right approach, you keep your dashboard performant and your code maintainable.

Up next: Real-time Form Validation where we will add robust error handling to these inputs.

Similar Posts