Form pendaftaran di sebuah platform booking yang saya bangun tiba-tiba mati total. Tombol submit diklik, tidak ada apa-apa yang terkirim, dan di console hanya ada satu baris merah yang bikin saya mengerutkan dahi:
Uncaught TypeError: Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'Pemicunya sederhana. Saya menangkap form dengan class-nya, lalu menyuapkan elemen itu ke FormData:
const form = document.querySelector('.signup-form');
const data = new FormData(form);Sekilas tidak ada yang salah. Class-nya benar, ejaannya benar, elemennya ada di DOM. Saya bahkan sempat console.log(form) dan dapat sebuah elemen yang valid — bukan null. Tapi FormData tetap menolaknya mentah-mentah.
Kenapa ini terjadi
Kuncinya ada di kata "HTMLFormElement". FormData itu rewel: konstruktornya cuma mau menerima elemen <form> asli. Bukan <div>, bukan <section>, bukan sembarang elemen yang kebetulan punya class yang sama. Kalau yang kamu kasih bukan <form>, dia langsung melempar TypeError.
Lalu kenapa querySelector saya mengembalikan elemen yang salah padahal class-nya benar? Karena markup-nya membungkus form asli di dalam sebuah div yang berbagi class yang sama. Saya buka lagi template-nya, dan inilah yang sebenarnya ada di sana:
<div class="signup-form">
<form id="signup">
<input name="email" type="email" />
<button type="submit">Daftar</button>
</form>
</div>Lihat masalahnya? Ada dua elemen yang berbeda, tapi class signup-form menempel di <div> pembungkus, bukan di <form>. Mungkin awalnya dari komponen styling, mungkin warisan dari markup lama — yang jelas wrapper-nya mencuri class itu.
document.querySelector('.signup-form') mengembalikan elemen pertama yang cocok dalam urutan dokumen. Dan elemen pertama yang cocok adalah <div> pembungkus, karena dia berada di luar dan muncul lebih dulu. Jadi variabel form saya itu memang valid dan ada isinya — tapi isinya sebuah <div>. FormData melihatnya, sadar itu bukan HTMLFormElement, dan menolak. Errornya jujur dari awal; saya saja yang salah membaca "form" sebagai jaminan bahwa saya benar-benar memegang sebuah form.
Inilah jebakan yang bikin bug ini licin. Selector-nya "berhasil". Tidak ada null. Tidak ada typo. Semuanya kelihatan benar sampai kamu sadar bahwa class bukan identitas — div pembungkus sering banget memakai ulang class form-nya dan diam-diam menutupi form aslinya.
Perbaikannya
Solusinya adalah berhenti memilih lewat class yang ambigu, dan menargetkan elemen <form>-nya secara spesifik. Karena form-nya punya id, cara paling bersih adalah memilih lewat id:
const form = document.querySelector('#signup');
const data = new FormData(form);Sekarang selector itu hanya bisa cocok dengan satu elemen — <form id="signup"> — dan FormData menerimanya tanpa protes.
Kalau karena suatu alasan kamu harus tetap pakai class itu, paksa selector-nya untuk hanya cocok dengan elemen form dengan menambahkan tag di depannya:
const form = document.querySelector('form.signup-form');
const data = new FormData(form);form.signup-form artinya "elemen <form> yang juga punya class signup-form". Div pembungkus tidak akan pernah lolos saringan ini, karena dia bukan <form>. Walaupun nanti markup-nya berubah dan class-nya tetap berserakan di mana-mana, selector ini tetap mengunci form aslinya.
Dan kalau kamu berada di dalam event handler — misalnya pada submit — ada cara yang lebih kokoh lagi: naik dari elemen yang memicu event ke form terdekat lewat closest:
form.addEventListener('submit', (e) => {
e.preventDefault();
const realForm = e.target.closest('form');
const data = new FormData(realForm);
});e.target.closest('form') berjalan ke atas dari target sampai ketemu <form> terdekat, tanpa peduli sama sekali soal class. Ini pendekatan yang paling tahan banting kalau struktur markup-mu sering berubah atau di luar kendalimu.
Saya akhirnya memilih versi #signup. Satu baris, niatnya jelas, dan tidak akan pernah ketukar dengan div pembungkus apa pun.
Pelajaran
FormData butuh elemen <form> yang asli — titik. Dia tidak akan mengambil field dari <div>, sebagus apa pun class-nya. Jadi saat kamu menyusun objek FormData, jangan pilih form lewat class yang mungkin juga dipakai elemen lain. Targetkan lewat #id atau lewat tag (form...), bukan lewat class yang dipinjam pembungkusnya.
Pelajaran yang lebih luas: di DOM, sebuah selector yang "berhasil" tidak berarti dia menemukan elemen yang kamu maksud. Class itu bisa dipakai berkali-kali; tag dan id jauh lebih jujur soal "ini benda apa". Kalau sebuah API memintamu jenis elemen yang spesifik, pilih elemennya dengan cara yang juga spesifik — biar error-nya tidak perlu mengajarimu pelan-pelan seperti yang dia lakukan ke saya.
