React Controlled Components

In the last lesson on React Forms you saw a form take what the user types and send it to you. This lesson is about who controls the box while they type, and why letting React hold that value makes everything easier.

🤔 Why hand control to React?

Here’s the pain. A plain HTML input keeps its own value inside the browser, and React does not know what’s in there unless it asks. That makes simple things harder than they should be.

  • You want to check what the user typed, like “is this email valid”, but the value is hidden in the DOM, not in your code.
  • You want to clear the box after submit, but you can’t easily, because React doesn’t own that value.
  • You want to change the text as they type, like force it to uppercase, and again you can’t, because the browser is holding it.

So the fix is simple. Let React hold the value in state instead. Then your code always knows the current value, and it can read it, check it, or change it any time.

🧩 What is a controlled component?

A controlled component is an input whose value is driven by React state. You wire it up with two props on the input.

  • value={something} tells the input what to show. It reads straight from your state.
  • onChange={...} runs every time the user types, and it saves the new text back into state.
  • So the input never decides its own value. React decides it, and the input just displays it.

Think of it like a box with a label on the front. You don’t write on the label directly, you change what’s in the box, and the label updates to match.

🔁 The one-way data loop

This is the heart of it. A controlled input runs in a small loop that always flows in one direction.

Here’s the loop, step by step:

  • React state holds the current value.
  • That value goes into the input through value={...}, so the box shows it.
  • The user types a key, and React fires onChange.
  • Inside onChange, you call the setter to save the new text into state.
  • State changed, so React re-renders, and the box shows the fresh value.

So it goes round and round on every keystroke. The picture below shows that same loop.

React state

value shown in input

user types a key

onChange runs

setState saves the text

💡 A controlled text input

Let’s build the smallest controlled input there is. It keeps the typed name in state and shows it back below the box.

import { useState } from "react";
function NameBox() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<p>Hello, {name}</p>
</div>
);
}

Let’s read those two props line by line, because that’s where the whole idea lives:

  • value={name} makes the box show whatever name holds right now. State drives the display.
  • onChange={(e) => setName(e.target.value)} runs on every keystroke. e.target.value is the new text, and setName saves it into state.
  • That setName call triggers a re-render, so value={name} reads the fresh value and the box updates.

So the box and the state are always the same. Type “Alex” and the <p> below shows “Hello, Alex” letter by letter.

Output

(user types "Alex")
Hello, Alex

💪 Why this is so powerful

Now the payoff. Because React always holds the value, you can do useful things with it any time, not just at submit.

  • You can validate it live. Check on every keystroke if the email has an ”@” and show a message right away.
  • You can transform it. Force the text to uppercase, or strip out spaces, before saving it to state.
  • You can clear it. Just call the setter with an empty string, like setName(""), and the box empties.
  • You can disable a button. If the box is empty, keep the submit button turned off.

So a controlled input isn’t just about reading the value. It gives you full control over the value at every moment, which is exactly what real forms need.

⚠️ A controlled input needs BOTH props

This is the mistake almost everyone hits once. If you set value but forget onChange, the input freezes and the user types and nothing happens.

Here’s the wrong way next to the right way:

// ❌ Wrong - value with no onChange, the box is frozen and read-only
<input value={name} />
// ✅ Right - value AND onChange, typing updates state and the box
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>

Here’s why that frozen box happens:

  • With only value={name}, the box is locked to whatever name holds. But nothing ever updates name, so the text never changes.
  • The user presses keys, but each render forces the box back to the old state value. It looks broken.
  • React even prints a console warning telling you to add an onChange or use readOnly.
  • Adding onChange closes the loop. Now typing updates state, state updates the box, and the input feels alive again.

value alone makes a read-only input

If you give an input a value but no onChange, React keeps forcing the box back to that value, so the user can’t type. Always pair value with an onChange that saves the new text into state.

🧰 The other option: uncontrolled inputs

There’s a second way to handle inputs, called an uncontrolled input, and it’s worth a quick mention so you know both exist.

  • An uncontrolled input keeps its own value in the DOM, like a plain HTML form. React does not hold it in state.
  • You set the starting text with defaultValue instead of value, and you skip onChange.
  • When you actually need the value, like on submit, you read it with a ref, which is a direct pointer to the DOM element.

This component reads the box only when it’s submitted, using a ref instead of state.

import { useRef } from "react";
function NameBox() {
const inputRef = useRef(null);
function handleSubmit() {
console.log(inputRef.current.value);
}
return (
<div>
<input defaultValue="" ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</div>
);
}

Most of the time you’ll reach for controlled inputs, because they give you the live value and all the control we talked about. Use uncontrolled ones for simple cases where you only need the value once, at the end.

🧩 What You’ve Learned

  • ✅ A controlled component is an input whose value is driven by React state
  • ✅ You wire it with value={...} to show the value and onChange to save new text into state
  • ✅ The data runs in a one-way loop: state → value shown → user types → onChange → setState → re-render
  • ✅ Because React always knows the value, you can validate it, transform it, or clear it any time
  • ✅ A controlled input needs BOTH props — value without onChange makes a frozen, read-only box
  • ✅ Uncontrolled inputs are the alternative: they use defaultValue and a ref to read the value when needed

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What makes an input a controlled component in React?

    Why: A controlled component gets its value from state via value={...} and updates that state on every keystroke through onChange. React owns the value.

  2. 2

    What happens if you set value on an input but leave out onChange?

    Why: With value but no onChange, React keeps forcing the box back to the state value, so typing does nothing. React also logs a warning. You need both props.

  3. 3

    In the controlled loop, what runs every time the user types a key?

    Why: Each keystroke fires onChange, which calls the setter with e.target.value. That updates state, React re-renders, and value={...} shows the fresh text.

  4. 4

    How does an uncontrolled input get its value when you need it?

    Why: An uncontrolled input keeps its value in the DOM. You set the starting text with defaultValue and read the current value through a ref, often on submit.

🚀 What’s Next?

Now you know how a controlled input works and why React holds the value. Next we’ll put that to work on the most common input of all, the text box, and look at the small details that make it feel right.

React Text Inputs

Share & Connect