Bab 13

Form & Validasi di Sisi Klien

Sepuluh bab terakhir halaman hanya bekerja satu arah, menampilkan dan bereaksi. Bab ini membuka arah sebaliknya, pengguna memberi data ke halaman. Dua konsepnya form, sarana HTML mengumpulkan input, lalu validasi, pemeriksaan isi input sebelum data dipakai atau dikirim. Ini juga bab penutup bagian JavaScript & Halaman.

Kuka duduk di kursi loket pendaftaran sambil mengisi formulir kertas dengan pulpen, ada kotak-kotak isian di kertas, dan satu kotak bertanda bintang wajib diisi.
Form, formulir di loket yang kotaknya siap diisi.

Mengapa form ada

Setiap interaksi selama ini menghasilkan satu perubahan di dalam halaman. Klik tombol menambah item, ketikan menyaring daftar. Tetapi tidak ada satu pun yang mengirim data keluar dari halaman untuk diproses lebih lanjut, misalnya mendaftar akun atau mengisi komentar. Untuk itu HTML menyediakan form. Form adalah sarana untuk mengumpulkan input dari pengguna menjadi satu kesatuan yang bisa dikirim. Isinya kotak-kotak isian, masing-masing satu <input>, dan setiap input diberi <label> agar jelas isinya untuk apa. Semua kotak itu dibungkus satu elemen <form>.

<form>
  <label for="email">Email</label>
  <input type="email" id="email">
  <button>Kirim</button>
</form>

Apa yang terjadi saat submit

Momen pengguna menekan tombol kirim disebut submit. Perilaku bawaan browser saat submit sederhana dan sudah tua: kumpulkan isi semua input, bawa keluar dari halaman, dan muat ulang halaman. Sepuluh bab lalu ini tidak pernah kita urus karena tidak ada form. Bab 9 sempat menunjuk masalah ini lewat event.preventDefault(), yang menghentikan perilaku bawaan sebuah kejadian. Saat itu contohnya menggantung, sekarang ia dipakai sungguhan. Dengan satu baris itu, submit tidak lagi memuat ulang halaman, dan JavaScript yang memegang datanya.

const form = document.querySelector("form");
form.addEventListener("submit", function (event) {
  event.preventDefault();
});

Mengapa validasi ada

Form mengumpulkan apa pun yang pengguna ketik, termasuk yang salah. Email tanpa tanda @, kolom nama yang kosong, umur berupa teks. Data salah yang lolos akan meracuni semua yang memakainya, dari tampilan sampai basis data. Validasi adalah pemeriksaan bahwa isi input sesuai bentuk yang diharapkan, dan penolakan sebelum data dipakai. Validasi di sisi klien berarti pemeriksaan itu jalan di browser, sebelum data berangkat ke mana pun. Keuntungannya kecepatan, pengguna tahu salahnya dalam sekejap tanpa menunggu perjalanan ke server.

Satu batas yang harus jujur diakui sejak awal. Validasi sisi klien bukan pengaman, kode di browser bisa ditembus atau dimatikan siapa pun yang tahu caranya. Server tetap wajib memeriksa ulang semua yang datang. Pemeriksaan klien itu layanan untuk pengguna yang baik hati, bukan tembok untuk yang jahat.

Validasi bawaan HTML

Cara termurah memvalidasi adalah meminta browser yang memeriksa. HTML menyediakan atribut untuk itu. Atribut required menandai kolom wajib isi, type="email" menuntut bentuk email, minlength dan maxlength membatasi panjang teks, min dan max membatasi angka. Browser memeriksa semuanya otomatis saat submit, memblokir pengiriman, dan menampilkan pesan kesalahan tanpa satu baris JavaScript.

<input type="email" id="email" required minlength="5">

Cara ini jalan dan layak dipakai, tetapi kendalinya terbatas. Pesannya gaya browser, bukan gaya halaman. Pemeriksaannya baru jalan saat submit, tidak bisa dipanggil kapan saja dari kode. Untuk itu HTML juga membuka pintu ke JavaScript.

Validasi dengan JavaScript, Constraint Validation API

Petugas loket mengembalikan formulir kertas ke Kuka melalui jendela loket sambil menunjuk satu kotak isian yang ditandai lingkaran merah, Kuka menunduk melihat kotak itu.
Validasi, petugas menunjuk kotak yang salah sebelum berkas masuk.

Di balik atribut-atribut tadi ada mesin yang bernama Constraint Validation API, kumpulan properti dan metode untuk memeriksa input dari kode. Setiap input punya metode checkValidity(), yang mengembalikan true bila isinya sah dan false bila tidak. Setiap input juga punya objek validity, yang menjelaskan sebab kegagalan lewat tanda boolean. validity.valueMissing berarti kolom wajib kosong. validity.typeMismatch berarti bentuk isinya tidak cocok dengan type, misalnya email tanpa @. Dengan dua itu halaman bisa menulis pesan kesalahannya sendiri, sespesifik apa pun sebabnya.

if (!email.checkValidity()) {
  if (email.validity.valueMissing) {
    pesan.textContent = "Email wajib diisi.";
  } else if (email.validity.typeMismatch) {
    pesan.textContent = "Bentuk email belum benar, harus ada tanda @.";
  }
}

Satu penghalang kecil yang wajib diketahui. Kalau form punya atribut seperti required, browser memeriksa sendiri dan memblokir submit sebelum kejadian submit sempat sampai ke JavaScript. Solusinya atribut novalidate pada form, yang mematikan pemeriksaan otomatis browser. Pemeriksaannya pindah tangan, atribut dan checkValidity() tetap bisa dipakai, tetapi kini JavaScript yang memutuskan kapan dan bagaimana pesan muncul.

Kapan memeriksa, saat submit atau saat mengetik

Waktu pemeriksaan adalah keputusan rasa, dan dua ujungnya punya harga. Memeriksa saat submit paling aman, pengguna mengisi tenang dulu, kesalahan dilaporkan sekali di satu tempat. Memeriksa saat mengetik terasa cepat, tetapi menyergap pengguna yang belum selesai, pesan "email tidak sah" muncul padahal ia sedang mengetik setengahnya. Kompromi yang umum dipakai halaman nyata: periksa pertama kali saat submit, dan setelah itu periksa langsung setiap ketikan, karena pengguna sudah tahu aturannya. Untuk bab ini pola saat submit saja sudah cukup.

Analogi: loket pendaftaran

Satu gambar untuk semuanya. Form adalah formulir kertas di loket pendaftaran, kotaknya sudah dicetak, ada yang bertanda wajib. Submit adalah menyerahkan formulir ke petugas loket. Validasi adalah petugas yang memeriksa berkas sebelum menerimanya, kotak wajib kosong ditunjuk, bentuk email yang salah ditandai, berkas dikembalikan seketika tanpa masuk ke dalam. preventDefault adalah petugas yang menahan berkas di loket alih-alih membiarkannya tersedot ke bagian belakang. Dan bagian belakang kantor yang memeriksa ulang semua berkas yang lolos loket, itulah server. Loket membuat pengunjung cepat tahu salahnya, bagian belakang yang menjaga kebenaran arsip.

Pasangan contoh

Contohnya form berlangganan email, satu input dan satu tombol. Versi vanilla menuliskan semuanya dengan tangan, pendengar submit, preventDefault, checkValidity(), pembacaan validity untuk memilih pesan, dan pengosongan input saat sah. Versi React menyimpan isi input di state lewat controlled input, yaitu input yang nilainya diatur value dari state dan setiap ketikan melapor lewat onChange, lalu onSubmit yang memeriksa dan menyimpan pesan error di state sendiri.

Vanilla JavaScript

form.addEventListener("submit", function (event) {
  event.preventDefault();

  if (email.checkValidity()) {
    pesan.textContent = "Terima kasih, " + email.value;
    email.value = "";
  } else if (email.validity.valueMissing) {
    pesan.textContent = "Email wajib diisi.";
  } else if (email.validity.typeMismatch) {
    pesan.textContent = "Bentuk email belum benar.";
  }
});

React

function Langganan() {
  const [email, setEmail] = React.useState("");
  const [pesan, setPesan] = React.useState("");

  function kirim(e) {
    e.preventDefault();
    if (!email.includes("@") || email.length < 5) {
      setPesan("Bentuk email belum benar.");
    } else {
      setPesan("Terima kasih, " + email);
      setEmail("");
    }
  }

  return (
    <form onSubmit={kirim}>
      <input value={email}
             onChange={(e) => setEmail(e.target.value)} />
      <button>Kirim</button>
      <p>{pesan}</p>
    </form>
  );
}

Perbedaan inti bab ini ada di sini. Di vanilla, nilai input dibaca dari DOM saat dibutuhkan, HTML adalah pemilik nilai dan JavaScript cuma penumpang yang menengok. Di React, keadaan terbalik, state adalah pemilik nilai, dan input hanya cermin yang mengikuti state lewat value. Itulah controlled input. Konsekuensinya pemeriksaan di React jalan atas state, bukan atas elemen DOM, jadi Constraint Validation API kalah praktis dibanding pemeriksaan sendiri pada string state. React tidak menghapus pekerjaan vanilla, ia memindahkan sumber kebenarannya ke tempat yang bisa di-render ulang secara konsisten.

Demo Hidup

Demo di bawah adalah formulir pendaftaran mini, satu input email wajib diisi. Submit kosong memunculkan pesan kolom wajib. Email tanpa @ memunculkan pesan bentuk tidak cocok. Isian sah mengganti form dengan pesan sukses. Halaman tidak pernah dimuat ulang di semua jalur itu, itulah preventDefault yang sedang bekerja.

Begini cara baca demonya. Form punya novalidate, jadi pemeriksaan required dan type="email" tidak memblokir submit sendiri, dan JavaScript yang memeriksa lewat checkValidity(). Kolom kosong terdeteksi lewat validity.valueMissing, email tanpa @ lewat validity.typeMismatch, dan pesannya ditulis halaman sendiri. Isian sah menyembunyikan form dan menampilkan pesan sukses. Kalau preventDefault dihapus dari kodenya, setiap klik Daftar akan memuat ulang halaman dan semua pesan hilang seketika.

Ringkasan bab ini. Form adalah sarana HTML mengumpulkan input, submit momen pengiriman dengan perilaku bawaan muat ulang halaman, dan preventDefault mengambil alihnya. Validasi memastikan isi input benar bentuknya sebelum dipakai, sisi klien memberi umpan balik cepat dan server tetap memeriksa ulang. Atribut required dan type memberi pemeriksaan gratis, Constraint Validation API dengan checkValidity() dan validity memberi kendali penuh, novalidate memindahkan tuasnya ke JavaScript. Bab berikutnya membuka bagian React, mengapa framework ada dan apa yang dibungkusnya dari semua yang sudah dipelajari sejauh ini.

Rujukan