Controlled Components
Controlled Components
◆ Definition
Controlled Component: An input element whose value is driven by React state at all times. The component re-renders on every change, and the displayed value always reflects state React is the single source of truth.
The pattern always requires two things: a value prop pointing to state, and an onChange handler that updates that state on every keystroke.
Basic Controlled Input
JSX — BasicInput.jsx
import { useState } from 'react'; function BasicInput() { // Step 1: Create state to hold the input's value const [name, setName] = useState(''); // Step 2: Handle changes — update state on every keystroke const handleChange = (e) => { setName(e.target.value); }; return ( <div> <label htmlFor="name">Full Name</label> <input id="name" type="text" value={name} // ← React controls the displayed value onChange={handleChange} // ← React updates on every keystroke placeholder="Enter your name" /> <p>Live preview: {name || 'stranger'}</p> </div> ); } export default BasicInput;
Complete Login Form
A real-world controlled form with two fields, a submit handler, and loading state:
JSX — LoginForm.jsx
import { useState } from 'react'; function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); // ← ALWAYS prevent default page reload first setIsLoading(true); try { await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); } finally { setIsLoading(false); } }; return ( <form onSubmit={handleSubmit}> <label htmlFor="email">Email</label> <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" required /> <label htmlFor="password">Password</label> <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required /> <button type="submit" disabled={isLoading}> {isLoading ? 'Logging in...' : 'Log In'} </button> </form> ); } export default LoginForm;
Always put
e.preventDefault()as the very first line of youronSubmithandler. If an error is thrown before it reaches that line, the browser will still submit and reload the page.
