React Controlled Components
Table of Contents + −
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.
💡 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 whatevernameholds right now. State drives the display.onChange={(e) => setName(e.target.value)}runs on every keystroke.e.target.valueis the new text, andsetNamesaves it into state.- That
setNamecall triggers a re-render, sovalue={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 whatevernameholds. But nothing ever updatesname, 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
onChangeor usereadOnly. - Adding
onChangecloses 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
defaultValueinstead ofvalue, and you skiponChange. - 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 andonChangeto 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 —
valuewithoutonChangemakes a frozen, read-only box - ✅ Uncontrolled inputs are the alternative: they use
defaultValueand arefto read the value when needed
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 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
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
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
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.