Problem Solved

React · controlled input

Home › Courses › React › Problems › Controlled input
⚛️ Problem · 5 min read

How to make a controlled input.
value + onChange. The whole pattern.

A controlled input's value lives in React state — every keystroke updates the state, every render reflects the state. The pattern is two props: value and onChange. That's it. But there's a warning, a trade-off, and a set of gotchas. This page covers all of it.

Short answer: Add both props: setName(e.target.value)} />. The input's value is always name. Typing fires onChange, which updates state, which re-renders with the new value. Never use value without onChange — React warns because the input becomes read-only. For simple forms where you only need the value on submit, use an uncontrolled input with defaultValue instead.

✅ Solution 1 — The canonical pattern

value + onChange. Every controlled input follows this.

The input's value prop is always the state. The onChange handler updates the state. React re-renders with the new value — the input displays it. The DOM never manages the value itself; React does. This is the entire mental model.

controlled-input.tsx
import { useState } from 'react';

// ─── The canonical controlled input ──────────
function SimpleForm() {
const [name, setName] = useState('');

return (
"text" value={name} // controlled onChange={e => setName(e.target.value)} // updates state placeholder="Your name" />

Hello, {name || 'stranger'}

); } // ─── Multiple fields: one state, one object ── interface FormData { name: string; email: string; } function MultiFieldForm() { const [form, setForm] = useState({ name: '', email: '' }); function updateFieldextends keyof FormData>(key: K, value: FormData[K]) { setForm(prev => ({ ...prev, [key]: value })); } return (
updateField('name', e.target.value)} /> updateField('email', e.target.value)} />
); } // ─── Different input types ─────────────────── function AllInputTypes() { const [text, setText] = useState(''); const [checked, setChecked] = useState(false); const [selected, setSelected] = useState('a'); const [textarea, setTextarea] = useState(''); return (
{/* Text input — value is string */} setText(e.target.value)} /> {/* Checkbox — checked is boolean */} "checkbox" checked={checked} onChange={e => setChecked(e.target.checked)} /> {/* Select — value is string */} {/* Textarea — same as text */}