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 |
errors.value = {}di baris pertama membuang peringatan dari kiriman sebelumnya. Tanpa baris itu, peringatan lama tertinggal walau isiannya sudah benar.errors.value.nama = "Nama wajib diisi"menambah satu pesan untuk kolomnama. Vue ikut mencatat properti baru di dalam ref, jadi<span v-if="errors.nama">langsung muncul.validasi()mengembalikantruekalauerrorskosong.kirim()berhenti direturnkalau ada yang salah.@submit.preventmenjalankankirimtanpa memuat ulang halaman (pelajaran event).- Modifier
v-modeldari pelajaran sebelumnya membantu di sini:.trimmembuang spasi di awal dan akhir, jadi nama berisi spasi saja dianggap kosong;.numbermengubah isi kolom jadi angka, jadiform.porsi < 1membandingkan angka..lazymenunda penulisan sampai eventchange, yaitu saat kolom ditinggalkan. - Preview editor latihan dan Playground mengizinkan form dikirim, jadi menekan tombol submit memicu
@submit. Tanpa.prevent, browser juga me-reload halamannya dan isian form-nya hilang, jadi tulis@submit.prevent.
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>
@blurjalan saat kursor meninggalkan kolom. Sejak itu peringatan kolom nama boleh tampil, dan ia ikut hilang di ketikan berikutnya yang membuat nama terisi.- Saat tombol Kirim ditekan, tandai semua kolom tersentuh (
tersentuh.nama = true,tersentuh.porsi = true), supaya kolom yang dilewati juga diberi tahu.
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>
:disabled="mengirim"mematikan tombol selama 1,5 detik itu. Klik kedua gak memanggilkirimlagi.- Labelnya ikut berganti jadi "Mengirim...", jadi pembeli tahu pesanannya sedang diproses.
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
- Peringatan lama tertinggal. Lupa
errors.value = {}(atauerror.value = ""dansuccess.value = falseseperti di latihan) membuat pesan kiriman sebelumnya tetap tampil. Kiriman yang benar harus membersihkan peringatan, dan kiriman yang gagal harus mencabut "Berhasil!". @clickdi tombol submit tanpa@submit.preventdi form. Di browser, tombol itu tetap mengirim form dan halamannya dimuat ulang. Pasang@submit.preventdi<form>.- Peringatan sebelum disentuh.
errorsyang dihitung tiap ketikan langsung tampil di form kosong. Tahan dengantersentuh. - Klik dua kali saat mengirim. Tanpa
:disabled, dapur menerima pesanan ganda. - Mengganti objek
reactive. Kosongkan denganObject.assign(form, awal).
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.