Form Lanjutan — Vue

Validasi Form const name = ref("") const error = ref("") const success = ref(false) const handleSubmit = () => { if (!name.value.trim()) { error.value = "Nama…

Form pesanan Warung Bu Sri sudah terikat ke data dengan v-model. Sekarang datanya harus diperiksa dulu sebelum dikirim: nama gak boleh kosong, porsi minimal 1. Pemeriksaan ini disebut validasi. Bayangin kasir yang menerima kertas pesanan. Sebelum diteruskan ke dapur, kasir mengecek tiap kolom, menulis catatan merah di samping kolom yang salah, dan baru meneruskan kertasnya kalau semua beres. Pertanyaannya tinggal kapan kasir mengecek: saat kertas diserahkan, saat pembeli selesai mengisi satu kolom, atau sambil pembeli menulis.

🧪 Tebakan cepat: Form-nya memeriksa di setiap ketikan, dan peringatannya dihitung langsung dari data. Halaman baru saja tampil, pembeli belum menyentuh apa pun. Apakah peringatan "Nama wajib diisi" sudah muncul? Tebak dulu, jawabannya ada di bagian "Kapan memeriksa: kirim, tinggalkan kolom, atau tiap ketikan".

Satu objek form, satu objek errors

Data form disimpan di satu objek reactive, dan pesan kesalahannya di satu objek errors. Kuncinya sama dengan nama kolomnya.

const awal = { nama: "", porsi: 1 }
const form = reactive({ ...awal })
const errors = ref({})

function validasi() {
  errors.value = {}
  if (!form.nama.trim()) errors.value.nama = "Nama wajib diisi"
  if (form.porsi < 1) errors.value.porsi = "Minimal 1 porsi"
  return Object.keys(errors.value).length === 0
}

function kirim() {
  if (!validasi()) return
  // aman: kirim pesanan ke dapur
}

---template---
<form @submit.prevent="kirim">
  <input v-model.trim="form.nama" placeholder="Nama pemesan" />
  <span v-if="errors.nama" class="error">{{ errors.nama }}</span>

  <input type="number" v-model.number="form.porsi" />
  <span v-if="errors.porsi" class="error">{{ errors.porsi }}</span>

  <button type="submit">Kirim</button>
</form>
Yang dikirim errors sesudah validasi() Yang tampil
nama kosong, porsi 1 { nama: "Nama wajib diisi" } peringatan di bawah kolom nama
nama "Sri", porsi 0 { porsi: "Minimal 1 porsi" } peringatan di bawah kolom porsi
nama "Sri", porsi 2 {} gak ada peringatan, pesanan lanjut

Kapan memeriksa: kirim, tinggalkan kolom, atau tiap ketikan

Waktu Cara menulisnya Rasanya bagi pembeli
Saat dikirim validasi() di dalam kirim() tenang saat mengisi, semua peringatan muncul sekaligus
Saat kolom ditinggalkan @blur="tinggalkan('nama')" peringatan muncul begitu pindah kolom
Tiap ketikan errors sebagai computed dari form langsung, tapi bisa terlalu cepat

Latihan pertama memakai pemeriksaan saat dikirim: mengetik saja gak mengubah hasil kiriman terakhir. "Berhasil!" tetap tampil walau namanya dihapus, sampai form dikirim lagi.

Untuk pemeriksaan tiap ketikan, errors dihitung dari data, jadi ia sudah berisi pesan sejak halaman tampil. Jawaban tebakannya: ya, "Nama wajib diisi" langsung muncul sebelum pembeli menyentuh apa pun. Itu jawaban tebakannya. Peringatan merah di form yang belum disentuh terasa seperti dimarahi duluan. Obatnya: catat kolom mana yang sudah disentuh, dan tampilkan peringatan hanya untuk kolom itu.

const tersentuh = reactive({ nama: false, porsi: false })
const errors = computed(() => {
  const e = {}
  if (!form.nama.trim()) e.nama = "Nama wajib diisi"
  if (form.porsi < 1) e.porsi = "Minimal 1 porsi"
  return e
})

---template---
<input v-model="form.nama" @blur="tersentuh.nama = true" />
<span v-if="tersentuh.nama && errors.nama" class="error">{{ errors.nama }}</span>

Mengirim: tombol mati selama menunggu

Mengirim ke server butuh waktu. Kalau tombolnya masih bisa ditekan, pembeli yang gak sabar menekannya dua kali dan dapur menerima dua pesanan yang sama.

const mengirim = ref(false)

function kirim() {
  if (!validasi()) return
  mengirim.value = true
  setTimeout(() => {          // pura-pura menunggu server 1,5 detik
    mengirim.value = false
    Object.assign(form, awal)
  }, 1500)
}

---template---
<button type="submit" :disabled="mengirim">
  {{ mengirim ? "Mengirim..." : "Kirim" }}
</button>

Mengosongkan form: Object.assign, bukan objek baru

Sesudah pesanan masuk, form dikosongkan lagi. Caranya: salin nilai awal ke objek yang sama.

Object.assign(form, awal)   // form.nama jadi "", form.porsi jadi 1

Godaan yang salah adalah membuat objek baru:

Ditulis Yang terjadi
const form = reactive(...), lalu form = reactive({ ...awal }) JavaScript menolak: Assignment to constant variable.
let form = reactive(...), lalu form = reactive({ ...awal }) jalan, tapi tampilan dan kode berpisah: kolomnya masih menampilkan "Sri", sedangkan kirim() membaca objek baru yang kosong dan menjawab "Nama wajib diisi"

Vue melacak objek reactive yang itu, bukan nama variabelnya. Kalau memang mau mengganti seluruh objek, simpan form di ref({ ...awal }) dan tulis form.value = { ...awal }.

⚠️ Jebakan yang sering ketemu

TL;DR: Simpan data form di satu objek reactive dan pesan kesalahan di errors, dengan kunci yang sama: errors.value.nama = "Nama wajib diisi". Mulai validasi() dengan errors.value = {} supaya peringatan lama hilang, dan panggil dari @submit.prevent. Periksa saat dikirim, saat kolom ditinggalkan (@blur), atau tiap ketikan (computed); yang terakhir butuh tersentuh supaya form kosong gak langsung merah. Matikan tombol dengan :disabled="mengirim" selama menunggu server, dan kosongkan form dengan Object.assign(form, awal), bukan objek baru. .trim membuang spasi, .number mengubah jadi angka, .lazy menunggu event change.

Yang akan kamu pelajari