Controlled and uncontrolled components in React explained

Controlled and uncontrolled components in React explained

Learn the difference between controlled and uncontrolled components in React, when to use each, and how they handle form fields with useState and useRef.

Form elements in HTML, <input>, <textarea>, <select>, keep their own internal state and update it based on user input. React gives you two ways to handle that state: let React own it (controlled), or let the DOM own it (uncontrolled). Understanding the difference is one of the most common React interview questions and a practical decision you make every time you build a form.

What are controlled and uncontrolled components in React?

A controlled component is a form element whose value is driven by React state. React is the single source of truth, every keystroke updates state, and the state value is what the input displays. An uncontrolled component is a form element that manages its own value in the DOM. React reads the value when needed using a ref, rather than tracking every change.

The simplest way to remember it: controlled components use useState, uncontrolled components use useRef.

Controlled vs uncontrolled components: key differences

Controlled Uncontrolled
Source of truth React state DOM
How value is read value prop + onChange ref.current.value
Re-renders on change Yes, every keystroke No
Validation Easy, on every keystroke Only on submit
File inputs Not supported Required
Code complexity Higher (more boilerplate) Lower for simple forms
React Hook Form Uses uncontrolled internally for performance Native fit

Controlled components

In a controlled component, every change to the input is reflected in React state, and the state value is what drives the input's displayed value. The state variable is the single source of truth.

A controlled component requires two things: a value prop tied to state, and an onChange handler that updates that state on every keystroke, including backspace.

import React, { useState } from "react";

function App(props) {
  const [message, updateMessage] = useState("");

  return (
    <div className="App" style={{ height: "20px" }}>
      <div className="container">
        <input
          type="text"
          placeholder="Enter message here.."
          value={message}
          onChange={(event) => updateMessage(event.target.value)}
        />
        <p>the message is: {message}</p>
      </div>
    </div>
  );
}

export default App;

In the App component above, message is the state variable and updateMessage is the setter. The input's onChange handler calls updateMessage with the new value on every keystroke, keeping state and UI in sync at all times.

Note that a parent <form> element is not required for an input to be controlled, the value prop and onChange handler are what make it controlled, not the surrounding structure.

Where controlled components shine:

  • Real-time validation (flag an invalid email on every keystroke)
  • Conditional UI (enable a submit button only when all fields are filled)
  • Derived state (show a character count as the user types)
  • Shared state between components (lift the value up to a parent)

The trade-off: On pages with many input fields, each one needs its own value prop and onChange handler. This is manageable but adds boilerplate. Libraries like React Hook Form solve this by using uncontrolled components under the hood for performance, while exposing a controlled-like API.

View the full demo here.

Uncontrolled components

In an uncontrolled component, the DOM stores the input's value. React does not track changes on every keystroke, instead, you read the value when you need it (typically on form submit) using a ref.

import { useRef } from "react";

const NameForm = () => {
  const inputRef = useRef(null);

  const handleSubmit = () => {
    alert(inputRef.current.value);
  };

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

export default NameForm;

In this example, inputRef is created with useRef and attached to the <input> via the ref attribute. The input's value is only read when the submit button is clicked, React does not re-render on every keystroke.

Uncontrolled components behave more like plain HTML form elements. The DOM, not the component, stores the data.

Where uncontrolled components make sense:

  • Simple submit-once forms (contact forms, search bars, newsletter signups) where you only need the value at the end
  • Integrating with non-React libraries that manipulate the DOM directly
  • File inputs, file inputs are always uncontrolled because their value is read-only for security reasons and cannot be set programmatically
  • Performance-sensitive forms with many fields where avoiding re-renders on every keystroke matters

View the full demo here.

When to use controlled vs uncontrolled components

The React team's general recommendation is to start with controlled components. They are more predictable, easier to debug, and work better with validation and complex form logic.

Use controlled components when:

  • You need to validate input on every keystroke
  • You need to enable or disable UI based on the current value
  • You need to share the input value with other components
  • You need to transform or format the value as the user types

Use uncontrolled components when:

  • The form is simple and you only need values on submit
  • You are integrating with a third-party DOM library
  • You are handling file uploads
  • You need to reduce re-renders for performance reasons
  • You are using React 19 form actions, which work naturally with uncontrolled inputs

A common mistake is switching a component between controlled and uncontrolled during its lifecycle. If you initialize an input with value={undefined} and later pass a real value, React will warn you. Pick one approach per input and stay consistent.

Controlled vs uncontrolled: performance

Controlled components re-render on every keystroke because each character triggers a state update. For most forms this is imperceptible. For forms with dozens of fields or very complex render logic, the cumulative re-renders can become noticeable.

This is why React Hook Form uses uncontrolled components internally, it avoids re-rendering the entire form on every keystroke by reading values from the DOM via refs at submit time, only triggering re-renders when necessary (e.g., for validation feedback).

If you are building a large, complex form and performance is a concern, consider React Hook Form rather than reimplementing the uncontrolled pattern yourself.

Choosing the right approach for your form

Both controlled and uncontrolled components have a place in React development. The choice depends on what the form needs to do.

For straightforward forms with minimal validation, uncontrolled components keep the code simple and avoid unnecessary re-renders. For forms that need real-time feedback, conditional logic, or integration with broader application state, controlled components give you the control you need.

If you are building React forms at scale and want to understand broader patterns like state management across components, explaining web app state management covers how React state works across component trees.

FAQs

1, What is the difference between controlled and uncontrolled components in React?

A controlled component is a form element whose value is managed by React state. The value prop is tied to a state variable, and an onChange handler updates that state on every keystroke, making React the single source of truth. An uncontrolled component lets the DOM manage the value. React reads it only when needed using a ref, without tracking every change.

2, When should I use controlled vs uncontrolled components in React?

Use controlled components when you need real-time validation, conditional UI, or to share the input value with other components. Use uncontrolled components for simple submit-once forms, file inputs, integration with non-React libraries, or when reducing re-renders matters for performance. The React team recommends starting with controlled components and switching to uncontrolled only when there is a specific reason.

3, Can you mix controlled and uncontrolled inputs in the same form?

Yes, you can have some inputs controlled and others uncontrolled in the same form. However, you cannot switch a single input between controlled and uncontrolled during its lifecycle. If you pass value={undefined} to an input and later pass a real value, React will throw a warning. Decide the approach per input and keep it consistent.

4, Why is file input always uncontrolled in React?

File inputs (<input type="file">) are always uncontrolled because their value is read-only for security reasons, JavaScript cannot set the value of a file input programmatically. You must always read the selected file via a ref or from the onChange event, even in an otherwise controlled form.

5, How does React Hook Form relate to controlled and uncontrolled components?

React Hook Form uses uncontrolled components internally by default. It reads input values from the DOM via refs at submit time rather than tracking every keystroke through state, which significantly reduces re-renders and improves performance for large forms. It exposes a controlled-like API (register, watch, setValue) without the re-render cost of true controlled inputs.

6, What happens if I initialize a controlled input with undefined?

If you pass value={undefined} to an input, React treats it as uncontrolled. If you later pass a real value, React will warn: "A component is changing an uncontrolled input to be controlled." Always initialize a controlled input with a defined value, use an empty string "" as the default rather than undefined or null.

7, Do controlled components affect performance?

Controlled components re-render the component on every keystroke because each character triggers a state update. For most forms this has no noticeable impact. For forms with many fields or expensive render logic, the cumulative re-renders can affect performance. In those cases, consider React Hook Form, which avoids this by using uncontrolled inputs under the hood.

About author

Asjad Khan is a Developer Advocate and Technical Writer passionate about building communities and making complex technologies simple and accessible. With experience in creating technical documentation, tutorials, and hands-on demos, he bridges the gap between engineering teams and developers by delivering clear, developer-first content. He has contributed to open-source projects, hosted workshops and hackathons, and actively engages with communities to drive adoption and learning. When not creating content or coding, Asjad can usually be found watching football and exploring new ideas in tech

Book A Call!

Reach Your Technical Audience And Drive Product Adoption.

We are engineers, developer advocates, and marketers passionate about creating lasting value for SaaS teams. Partner with us to create the human-written developer marketing, SEO, demand-gen, and documentation content.

Get started

*35% less cost, risk-free, no lock-in.

Logo 1
Logo 2
Logo 3
Logo 4
Logo 5
Logo 6
Logo 7
Logo 8
Logo 9
Logo 10
Logo 11
Logo 12
Logo 13
Logo 14
Logo 15
Logo 16
Logo 17
Logo 18