Form Lanjutan — React

Form login 2 field cukup pakai useState + validasi sederhana. Tapi form registrasi 10 field dengan validasi kompleks (email format, password 8+ karakter…

Form yang baik memeriksa isiannya sebelum diterima, lalu memberi tahu apa yang kurang tepat, tepat di kolom yang salah. Pemeriksaan ini namanya validasi. Bayangin petugas pendaftaran di loket. Kamu serahkan formulir, dia periksa. Kalau nama kosong, dia tunjuk kolomnya dan bilang "Nama wajib diisi". Kalau semuanya beres, dia cap "Diterima". Ada petugas yang memeriksa saat formulir diserahkan (saat kirim), ada juga yang ikut membaca sambil kamu menulis (sambil mengetik). Di React, dua-duanya kamu tulis sendiri dengan state.

🧪 Tebakan cepat: kamu kirim nama Budi dan muncul Berhasil!. Lalu kamu hapus namanya tanpa menekan Kirim. Berhasil! masih tampil atau hilang? Tebak dulu, jawabannya ada di bagian "Periksa saat kirim".

Periksa saat kirim

function App() {
  const [name, setName] = useState("")
  const [error, setError] = useState("")
  const [success, setSuccess] = useState(false)

  const handleSubmit = (e) => {
    e.preventDefault()
    if (!name.trim()) {
      setError("Nama wajib diisi")
      setSuccess(false)
    } else {
      setError("")
      setSuccess(true)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input id="name" value={name} onChange={(e) => setName(e.target.value)} />
      {error && <span id="error">{error}</span>}
      {success && <span id="success">Berhasil!</span>}
      <button type="submit">Kirim</button>
    </form>
  )
}

Ini yang terjadi di kolom Nama di Lab, langkah demi langkah:

Langkah name error success Tampil
Kirim dengan kolom kosong "" "Nama wajib diisi" false Nama wajib diisi
Ketik Budi "Budi" "Nama wajib diisi" false Nama wajib diisi
Kirim "Budi" "" true Berhasil!
Hapus namanya "" "" true Berhasil!
Kirim lagi "" "Nama wajib diisi" false Nama wajib diisi

Mengetik cuma mengubah name. error dan success baru berubah saat kirim. Jadi setelah namanya dihapus, Berhasil! masih tampil, karena dia hasil kiriman terakhir. Itu jawaban tebakannya.

Periksa sambil mengetik

Kadang orang perlu tahu salahnya selagi mengetik, misalnya syarat password. Caranya: jangan simpan pesannya di state, hitung langsung dari isiannya saat render. Tiap ketikan memanggil setter, React render ulang, dan syaratnya diperiksa lagi.

function App() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")

  return (
    <form>
      <input id="email" value={email} onChange={(e) => setEmail(e.target.value)} />
      {!email.includes("@") && <span id="email-error">Email harus mengandung @</span>}

      <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      {password.length < 6 && <span id="password-error">Password minimal 6 karakter</span>}
    </form>
  )
}

Ketik [email protected]: begitu @ masuk, pesan email lenyap. Ketik rahasiaku123: di huruf keenam, pesan password lenyap. Kekurangannya, pesan merahnya sudah muncul sebelum orang sempat mengetik apa pun. Jalan tengah yang sering dipakai: tampilkan pesan sebuah kolom setelah kolom itu ditinggalkan, lewat event onBlur. Coba ketiga caranya di Lab.

Banyak kolom, satu handler

Kamu sudah ketemu ini di materi Form: satu object state untuk semua kolom, dan satu handler untuk semuanya.

function DaftarForm() {
  const [form, setForm] = useState({ name: "", email: "", password: "" })

  function handleChange(e) {
    setForm((prev) => ({ ...prev, [e.target.name]: e.target.value }))
  }

  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>
  )
}

Controlled atau uncontrolled

Semua contoh di atas adalah controlled component: nilainya disimpan di state React, lewat value dan onChange. Kebalikannya uncontrolled component: nilainya disimpan di DOM sendiri, dan kamu baru membacanya saat butuh, lewat ref.

function CariForm() {
  const kotaRef = useRef(null)

  function handleSubmit(e) {
    e.preventDefault()
    console.log("Cari:", kotaRef.current.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={kotaRef} defaultValue="Bandung" />
      <button type="submit">Cari</button>
    </form>
  )
}
Controlled Uncontrolled
Nilainya disimpan di state React DOM
Dibaca lewat variabel state ref, saat dibutuhkan
Validasi sambil mengetik bisa gak bisa
Cocok untuk hampir semua form form sederhana tanpa validasi real-time, atau library non-React yang mengurus input-nya sendiri

📌 Kalau form-nya makin besar

Form 10 kolom dengan banyak aturan cepat jadi ratusan baris useState dan if. Di app sungguhan, orang biasanya memakai React Hook Form (mengurus nilai, error, dan status kirim) bersama Zod (aturan validasinya ditulis sekali sebagai schema). Dasarnya tetap yang kamu pelajari di sini: periksa, simpan hasilnya, tampilkan pesannya di dekat kolomnya.

⚠️ Jebakan yang sering ketemu

TL;DR: Validasi saat kirim: di onSubmit, panggil e.preventDefault(), periksa isiannya, simpan hasilnya di state (error, success), dan atur dua-duanya di tiap cabang. Hasil itu bertahan sampai kiriman berikutnya, mengetik gak mengubahnya. Validasi sambil mengetik: hitung syaratnya langsung saat render (!email.includes("@"), password.length < 6) dan tampilkan pesannya dengan syarat && <span>, supaya hilang dari halaman begitu sah. onBlur adalah jalan tengahnya. Banyak kolom cukup satu handler dengan [e.target.name]. Controlled menyimpan nilai di state, uncontrolled menyimpannya di DOM dan dibaca lewat ref, cocok untuk form sederhana tanpa validasi real-time. Server tetap wajib memeriksa ulang.

Yang akan kamu pelajari