React: Membangun Antarmuka Pengguna · 9/9
Form di React
Input terkontrol, umpan balik validasi, dan submit tanpa menghilangkan pekerjaan pengguna.
Baca 30 menit
Setelah pelajaran ini kamu bisa
- Menjelaskan bedanya input terkontrol dan tidak terkontrol
- Memilih di antaranya dengan sadar
- Menangani submit, state pending, dan error
- Memakai `useActionState` dari React 19 dengan sebuah Server Action
Ada dua cara memiliki sebuah field form. Tidak ada yang salah, dan sebagian besar tutorial hanya menampilkan satu.
| Terkontrol | Tidak terkontrol | |
|---|---|---|
| Nilainya tinggal di | State React | Elemen DOM-nya |
| Re-render setiap ketikan | Ya | Tidak |
| Membaca nilainya | Kapan saja, dari state | Saat submit, lewat FormData |
| Dibutuhkan untuk | Validasi langsung, format, field yang saling bergantung | Submit-dan-kirim biasa |
| Kode yang dibutuhkan | Lebih banyak | Hampir tidak ada |
// Controlled: state is the single source of truth.
function ControlledSearch() {
const [query, setQuery] = useState("");
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{/* Only possible because the value is in state: */}
<p>{query.length} characters</p>
<button disabled={query.length < 3}>Search</button>
</>
);
}
// Uncontrolled: the DOM keeps the value, and we read it on submit.
function UncontrolledSearch({ onSearch }) {
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSearch(new FormData(e.currentTarget).get("query"));
}}
>
<input name="query" defaultValue="" />
<button type="submit">Search</button>
</form>
);
}defaultValue, bukan value, untuk yang tidak terkontrol — memakai value tanpa onChange menghasilkan field yang tidak bisa diketik penggunanya, dan itu kesalahan pertama yang sangat umum.Submit punya tiga state
Setiap form yang bicara ke server harus menangani state pending dan error, bukan cuma sukses. Ini discriminated union dari modul TypeScript, diterapkan pada UI yang paling umum ada.
State machine yang benar-benar dibutuhkan sebuah form, sebagai reducer. Perhatikan transisi mana yang memang tidak mungkin.
Hasil
Tekan Jalankan untuk melihat hasilnya.
Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.
React 19: form action
React 19 menangani sebagian besar pembukuan itu untukmu. Sebuah form bisa menerima function sebagai action-nya, dan useActionState melacak nilai yang dikembalikan serta penanda pending-nya.
"use client";
import { useActionState } from "react";
import { signInAction } from "./actions";
export function SignInForm() {
// [ whatever the action returned, the function to pass to <form>, is it pending ]
const [result, formAction, pending] = useActionState(signInAction, undefined);
return (
<form action={formAction}>
<label htmlFor="name">Name</label>
<input id="name" name="name" required autoComplete="username" />
<label htmlFor="password">Password</label>
<input id="password" name="password" type="password" required
autoComplete="current-password" />
{result?.error && (
<p role="alert" aria-live="polite">{messages[result.error]}</p>
)}
<button type="submit" disabled={pending}>
{pending ? "Signing in..." : "Sign in"}
</button>
</form>
);
}features/auth/auth-form.tsx dari proyek ini, diringkas. Baca yang aslinya — bentuknya sama dengan pesan yang dilokalkan dan aria-invalid.- `pending` sudah gratis. Tidak ada
useStateuntuk melacaknya, dan tidak mungkin lupa mematikannya di jalur error. - Action-nya menerima `FormData`. Input tidak terkontrol, jadi tidak perlu state per field.
- Ia bekerja sebelum hydration.
<form action>tetap ter-submit meski JavaScript belum termuat — progressive enhancement secara default. - Submit ganda sudah ditangani. React mematikan pengiriman ulang selama pending.
Tugas praktik
Di proyek ini, buka features/auth/auth-form.tsx dan petakan setiap bagiannya ke pelajaran ini: input mana yang tidak terkontrol, pending datang dari mana, bagaimana error-nya di-render secara aksesibel, dan kenapa locale dikirim lewat input tersembunyi. Lalu rusak dengan sengaja — hapus disabled={pending} lalu submit dua kali dengan cepat. Perhatikan apa yang dilakukan server terhadapnya.
Hasil yang diharapkan
Pendaftaran kedua ditolak sebagai nama yang sudah dipakai — constraint UNIQUE-nya adalah pengaman sebenarnya.