Episode ini membahas form handling dengan controlled dan uncontrolled components, lalu React Hook Form beserta validation rules dan schema validation dengan Yup atau Zod. Kalian juga belajar accessibility form dan input feedback yang jelas.

Form adalah pintu masuk utama data dari pengguna, tapi juga sumber bug paling umum: validasi yang tidak konsisten, pesan error yang membingungkan, dan aksesibilitas yang diabaikan. Episode 11 membuat kalian menguasai form React secara menyeluruh, mulai dari dasar — controlled dan uncontrolled components — lalu beralih ke React Hook Form dengan schema validation memakai Yup atau Zod, dan diakhiri aturan accessibility serta input feedback yang nyaman dipakai semua orang.
Seperti episode 5, controlled component menaruh nilai input di state:
import { useState } from "react"
function Form() {
const [nama, setNama] = useState("")
const submit = (e) => {
e.preventDefault()
console.log("Dikirim:", nama)
}
return (
<form onSubmit={submit}>
<input value={nama} onChange={(e) => setNama(e.target.value)} />
<button type="submit">Kirim</button>
</form>
)
}e.preventDefault() menghentikan reload halaman default, lalu handleSubmit memproses data. Controlled component paling cocok saat nilai input dipakai untuk validasi langsung atau UI lain.
Untuk form yang sederhana dan sekali kirim, uncontrolled components memakai ref dan membaca nilai saat submit, tanpa re-render setiap ketikan:
import { useRef } from "react"
function Form() {
const namaRef = useRef(null)
const handleSubmit = (e) => {
e.preventDefault()
console.log("Dikirim:", namaRef.current.value)
}
return (
<form onSubmit={handleSubmit}>
<input ref={namaRef} defaultValue="" />
<button type="submit">Kirim</button>
</form>
)
}namaRef.current.value membaca nilai input saat submit. Tanpa onChange, komponen tidak re-render tiap ketikan — lebih hemat untuk form besar. React Hook Form di bagian berikut memakai pendekatan ini secara internal.
React Hook Form menggabungkan kecepatan uncontrolled dengan kemudahan validasi:
npm install react-hook-formimport { useForm } from "react-hook-form"
function Form() {
const { register, handleSubmit, formState } = useForm()
const { errors } = formState
const onSubmit = (data) => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email", { required: "Email wajib diisi" })} />
{errors.email && <p>{errors.email.message}</p>}
<button type="submit">Kirim</button>
</form>
)
}register("email", { required: "Email wajib diisi" }) mendaftarkan input ke form dengan validation rules. handleSubmit(onSubmit) memvalidasi dulu — fungsi onSubmit hanya berjalan saat semua rule lolos, dan error tersedia di formState.errors.
Tanpa schema pun React Hook Form menyediakan rule dasar seperti min, max, dan maxLength yang ditulis di dalam register, sama seperti required pada contoh di atas. Untuk aturan yang lebih kompleks dan reusable, schema validation di bagian berikut adalah pilihan yang lebih rapi.
Schema validation mendefinisikan aturan sekali lalu dipakai untuk validasi dan tipe sekaligus. Zod adalah pilihan modern:
npm install zod @hookform/resolversimport { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
const skema = z.object({
email: z.string().email("Format email salah"),
password: z.string().min(8, "Minimal 8 karakter"),
})
function Form() {
const { register, handleSubmit, formState } = useForm({
resolver: zodResolver(skema),
})
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("email")} />
{formState.errors.email && <p>{formState.errors.email.message}</p>}
<input type="password" {...register("password")} />
{formState.errors.password && <p>{formState.errors.password.message}</p>}
<button type="submit">Daftar</button>
</form>
)
}zodResolver(skema) menghubungkan schema Zod ke React Hook Form. z.object, z.string().email, dan z.string().min mendefinisikan aturan; pesan error diambil otomatis dari schema. Di TypeScript, tipe data form bisa di-infer langsung dari schema. Yup punya API serupa dengan string().email().min(8) — pilihan di antara keduanya soal preferensi, dan keduanya berjalan melalui @hookform/resolvers yang sama.
Form yang aksesibel butuh tiga hal: label yang terhubung ke input, atribut aria-* yang menjelaskan status, dan pesan error yang bisa dibaca screen reader:
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<label htmlFor="email">Email</label>
<input
id="email"
{...register("email", { required: "Email wajib diisi" })}
aria-invalid={errors.email ? "true" : "false"}
/>
{errors.email && <p role="alert">{errors.email.message}</p>}
</form>htmlFor dan id menghubungkan label ke input. aria-invalid memberi tahu screen reader bahwa input bermasalah, dan role="alert" mengumumkan pesan error saat muncul. noValidate mematikan validasi bawaan browser agar React Hook Form yang menanganinya konsisten.
Episode 11 membuat kalian mahir form React: perbedaan controlled dan uncontrolled components, React Hook Form dengan validation rules, schema validation dengan Zod dan Yup, plus accessibility dan input feedback yang ramah pengguna.
Inti yang harus dibawa pulang:
register, error muncul di formState.errors.htmlFor, aria-invalid, dan role="alert" membuat form aksesibel.Di episode 12 selanjutnya kita akan membahas secure frontend & auth patterns — authentication patterns untuk SPA, JWT dan refresh tokens dengan secure storage, protecting routes dan role-based access control, serta mitigasi CSRF dan XSS saat menangani input pengguna. Aplikasi kalian mulai berhadapan dengan keamanan nyata.