Topic 21 of 54
Controlled Components
Overview
In standard HTML, form elements (like `<input>`, `<select>`, `<textarea>`) maintain their own internal state based on user input. In React, we generally prefer 'Controlled Components'. This means React state is the 'single source of truth'. The input's value is driven entirely by a state variable, and any user typing triggers an `onChange` event that updates that state. This gives you absolute control to validate, format, or block input as it happens.
Syntax
By tying the `value` prop to the state variable `name`, the input cannot show anything other than what is in the state. If you remove `onChange`, the input will become read-only and freeze!
A Basic Controlled Input
jsx
import { useState } from 'react';
function ControlledInput() {
const [name, setName] = useState("");
const handleChange = (e) => {
// We can intercept and format the text here if we want
// e.g., setName(e.target.value.toUpperCase());
setName(e.target.value);
};
return (
<div>
{/* The 'value' prop forces the input to match React state */}
<input
type="text"
value={name}
onChange={handleChange}
/>
<p>Hello, {name}!</p>
</div>
);
}Common Pitfalls
- Providing a `value` prop but forgetting the `onChange` handler. The React console will scream at you, and the input will be completely frozen.
- Setting the initial state of an input to `null` or `undefined`, which React interprets as an 'Uncontrolled' component, causing warnings when you type.
Interview Tips
- Be able to clearly define a Controlled Component: 'An input whose value is controlled by React state via the value prop and an onChange handler.'
Real-World Example
Restricting input to only numbers, like a credit card or zip code field.
example
jsx
function ZipCodeInput() {
const [zip, setZip] = useState("");
const handleInput = (e) => {
const rawValue = e.target.value;
// Regex to allow only numbers. If it has letters, we ignore it.
if (/^\d*$/.test(rawValue)) {
setZip(rawValue);
}
};
return (
<input
type="text"
value={zip}
onChange={handleInput}
maxLength="5"
placeholder="12345"
/>
);
}