Skip to content
C Codeloom
React

React Forms: Controlled vs Uncontrolled Inputs Explained

Understand the difference between controlled and uncontrolled inputs in React, when to use each, and how to combine them with refs and form libraries.

·4 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What controlled and uncontrolled inputs really mean
  • When to use each pattern
  • How refs work with form fields
  • How form libraries blend both approaches
  • Common pitfalls with default values

Prerequisites

  • Comfortable with JS and HTML
  • Basic React component knowledge

What and Why

Forms are the place where most React applications interact with users. React gives you two main patterns to manage input data: controlled and uncontrolled. Both work, both ship to production, but they lead to very different code shapes and trade-offs.

A controlled input has its value driven by React state. Every keystroke updates state, and the state value is fed back into the input. The component owns the truth. An uncontrolled input keeps its value inside the DOM, the way a plain HTML form does. React reads it only when you ask, usually through a ref or on submit.

Picking the right model affects performance, validation, testability, and how easy your form is to integrate with libraries like React Hook Form or Formik.

Mental Model

Think of a controlled input as a puppet on strings. React holds every string. When the user types, the event tells React, “I want to change this letter.” React decides whether to allow it, transforms it if needed, and pushes the new value back down. Nothing displays unless React says so.

An uncontrolled input is more like a notepad on the user’s desk. They write whatever they want, and you can walk over and read it whenever you choose. The DOM holds the value. React only knows what is in the input if you actively ask via a ref.

Hands-on Example

A controlled email field looks like this:

function EmailField() {
  const [email, setEmail] = useState('');
  return (
    <input
      type="email"
      value={email}
      onChange={(e) => setEmail(e.target.value.toLowerCase())}
    />
  );
}

Notice how we transform the value to lowercase before storing it. That kind of inline validation or formatting is the killer feature of controlled inputs.

The uncontrolled version uses defaultValue and a ref:

function EmailField() {
  const ref = useRef(null);
  function handleSubmit() {
    console.log(ref.current.value);
  }
  return <input type="email" defaultValue="" ref={ref} />;
}

The DOM stores the value. We only touch it on submit. There are fewer renders, but we cannot easily mask the input as the user types.

Controlled:
user types -> onChange -> setState -> re-render -> input shows new value

Uncontrolled:
user types -> DOM updates internally -> on submit -> read ref.value
Controlled vs uncontrolled data flow

For most real forms you mix the two. Libraries like React Hook Form register inputs as uncontrolled by default for performance, then re-render selectively when validation needs to surface an error.

Common Pitfalls

The classic mistake is switching an input between controlled and uncontrolled mid-life. If value starts as undefined and later becomes a string, React logs a warning and the cursor can jump. Always initialize state to "", never undefined.

Another trap is forgetting that defaultValue is only read on the first render. Changing it later does nothing. If you need to reset an uncontrolled form, change the input’s key to force a remount.

Performance is another gotcha. A giant form built entirely from controlled inputs re-renders the whole tree on every keystroke. If you notice typing lag, consider moving state lower in the tree or switching to an uncontrolled library.

Finally, watch out for radios and checkboxes. Use checked plus onChange for controlled, and defaultChecked for uncontrolled. Mixing them silently breaks the input.

Best Practices

  • Default to controlled inputs while learning. The data flow is easier to reason about.
  • Reach for uncontrolled patterns when you have many fields or performance matters.
  • Use a form library once you need validation, error messages, and submission states.
  • Always initialize controlled state with a safe value of the right type.
  • Co-locate each input’s state with the smallest component that needs it.

Wrap-up

Controlled inputs put React in charge, which makes formatting and validation easy at the cost of re-renders. Uncontrolled inputs let the DOM hold the value, which is fast but less reactive. Real apps blend both, and modern libraries hide the choice behind a clean API. Once you understand the trade-off, picking the right tool for each form takes seconds instead of minutes.