Form daftar akun, form login, form pencarian, form checkout — tiap aplikasi web pasti punya form. Cara React handle form unik: bukan baca nilai input dari DOM (kayak vanilla), tapi simpan nilai input di React state. Setiap keystroke → update state → React re-render dengan nilai baru. Pattern ini namanya controlled component — React jadi "sumber kebenaran tunggal", DOM cuma reflect state.
🤔 Kenapa rumit dibanding vanilla? Vanilla HTML form: ketik di input, browser handle storage internal, baca pakai
input.valuesaat submit. React controlled: tiap ketik trigger event → setState → re-render. Kelihatan ribet, tapi reward-nya GEDE: validasi real-time gampang, format on-the-fly (uppercase, currency formatting), conditional disable, undo/redo — semuanya gratis karena state sentralisasi.
function LoginForm() {
const [email, setEmail] = useState("")
return (
<input
value={email} // state → DOM
onChange={(e) => setEmail(e.target.value)} // DOM → state
/>
)
}
Loop dua arah: state → input value (display) + input change → state (capture). Itu yang bikin "controlled".
Controlled vs Uncontrolled
| Controlled | Uncontrolled |
|---|---|
| Nilai = React state | Nilai = DOM langsung |
value={state} + onChange |
defaultValue (optional) |
| Validasi real-time gampang | Akses pakai useRef |
| Re-render tiap keystroke | Gak re-render saat ketik |
| Default modern React | Hanya untuk kasus khusus |
Form Submission
function App() {
const [name, setName] = useState("")
const handleSubmit = (e) => {
e.preventDefault() // WAJIB — cegah reload halaman
console.log("Submitted:", name)
// Kirim ke API: fetch("/api/users", { method: "POST", ... })
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Kirim</button>
</form>
)
}
Multiple Inputs — Object State
function RegisterForm() {
const [form, setForm] = useState({
name: "", email: "", password: "",
})
const handleChange = (e) => {
const { name, value } = e.target
setForm(prev => ({ ...prev, [name]: value })) // dynamic key
}
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
</form>
)
}
Pattern ini ngirit code — satu handler buat banyak field via name attribute.
🎭 Analogi sehari-hari
Controlled form itu kayak buku tulis di kelas. Tiap kamu nulis huruf di buku, guru langsung nyatat ulang di papan tulis (state). Pas guru hapus papan, buku kamu juga otomatis ke-update — papan tulis jadi sumber kebenaran. Vanilla form lebih kayak buku tulis pribadi: kamu nulis sendiri, guru cuma ngecek pas ulangan (saat submit). Cara React ribet di awal — tiap keystroke ada interaksi — tapi pas mau validasi real-time ("password kurang 8 karakter") atau format on-the-fly ("auto-uppercase"), kekuatan pattern ini langsung kelihatan.
⚠️ Jebakan yang sering ditemui
- Lupa
value— input kerja tapi gak controlled. State update, tapi DOM tetep nilai lama. Wajibvalue={state}+onChange. - Lupa
e.preventDefault()di submit — form reload halaman, state hilang, console.log kena reload. WAJIB. - Mutate state object —
form.email = e.target.value; setForm(form)= SALAH (mutate, React gak detect). Pakai spread:setForm({...form, email: e.target.value}). - Initial value
null/undefineddi controlled — React warning "switching from uncontrolled to controlled". Initial harus""(string empty), bukan undefined. onChangedipanggil per-keystroke buat input besar — bisa lambat untuk autocomplete dengan filtering complex. Pakai debounce.- Checkbox:
valuevschecked— checkbox/radio pakechecked={state}, BUKANvalue. - Number input —
e.target.valueselalu STRING, bahkan untuktype="number". Convert manual:Number(e.target.value).
🎯 State form — pilih bentuk apa?
- 1-2 field → useState terpisah (
useState("")per field)- 3-5 field related → useState object (
{ name, email, ... }) + spread updater- Form kompleks (10+ field, validasi rumit, multi-step) → library: React Hook Form atau Formik (jangan bikin sendiri)
- Form yang gak butuh re-render saat ketik (search box dengan debounce) → uncontrolled + useRef
- Form fields dinamis (tambah/hapus field array) → useFieldArray dari React Hook Form
Aturan praktis: useState manual buat form sederhana. React Hook Form pas form mulai gemuk (validasi + nested + dynamic).
TL;DR: Controlled form = nilai input simpan di React state. value={state} + onChange={e => setState(e.target.value)}. WAJIB e.preventDefault() di submit (cegah reload). Multi-field? Pakai object state + spread updater dengan dynamic key [e.target.name]. Number input → e.target.value selalu string (convert manual). Checkbox pakai checked bukan value. Form kompleks → React Hook Form.