querySelector mengembalikan null karena kode query berjalan sebelum browser membuat elemennya. Panggil query ulang tepat setelah fungsi render selesai, bukan di atasnya. Untuk list dan tombol yang terus berubah, pasang satu listener di induknya lalu baca target klik dengan closest.
Kenapa Query Mengembalikan Null pada Elemen Dinamis
Browser mengeksekusi JavaScript dari atas ke bawah. document.querySelector(“.item”) yang dipanggil sebelum render hanya membaca DOM lama, jadi hasilnya null walau HTML target sudah tertulis di kode render.
- Render dari fetch atau setTimeout selesai belakangan, sedangkan query di luar callback jalan lebih dulu.
- Penugasan container.innerHTML = … menghapus node lama beserta listenernya, lalu membuat node baru yang belum dikenal variabel lama.
- Selector salah menghasilkan null permanen, sedangkan elemen belum render menghasilkan null sementara dan normal setelah query ulang.
Cek cepatnya: log container.children.length sebelum query. Nol berarti render belum terjadi, bukan selector rusak.
Pola Aman Mengambil Elemen Setelah Render
- Query ulang setelah render: panggil renderList() dulu, baru querySelectorAll.
- Event delegation: pasang satu addEventListener di kontainer induk agar tombol baru otomatis ikut tertangani.
- MutationObserver: pakai hanya bila render dilakukan kode pihak lain, seperti widget atau library, sehingga script kita tidak bisa memanggil query ulang langsung.
Kode Minimal untuk Ambil dan Pasang Event
Ikuti urutan ini agar tidak dapat null:
- Render list ke dalam kontainer.
- Query ulang elemen hasil render bila perlu dibaca langsung.
- Pasang delegation di induk untuk semua klik tombol dinamis.
const list = document.querySelector("#list");
function renderList(data) {
list.innerHTML = data.map((item) =>
`<li>${item}<button data-hapus="1">Hapus</button></li>`
).join("");
}
renderList(["Andi", "Budi"]);
const buttons = list.querySelectorAll("button");
list.addEventListener("click", (event) => {
const tombol = event.target.closest("[data-hapus]");
if (!tombol) return;
tombol.closest("li").remove();
});
renderList mengisi kontainer dulu sebelum query apa pun. querySelectorAll sesudahnya membaca node baru. closest(“[data-hapus]”) memastikan hanya klik tombol hapus yang diproses, walau user mengklik ikon di dalam tombol.
Tabel Pilihan Teknik Berdasarkan Kasus
| Metode | Cocok Untuk | Catatan |
|---|---|---|
| Query ulang | Render milik sendiri | Panggil setelah render selesai |
| Delegation | List dan tombol dinamis | Satu listener di induk |
| MutationObserver | Render library pihak lain | Putuskan dengan disconnect |
Untuk teks biasa pakai textContent, bukan innerHTML. Contoh di atas memakai template karena membuat struktur li dan button. Simpan observer dalam variabel dan panggil observer.disconnect() setelah elemen target muncul agar callback tidak jalan terus dan memori tidak bocor.
FAQ
Apakah harus query ulang setiap selesai render?
Ya, karena innerHTML mengganti node lama dengan node baru yang tidak dikenal variabel lama.
Kapan memakai MutationObserver dibanding delegation?
Pakai MutationObserver hanya saat render dikendalikan kode lain yang tidak bisa disisipi query ulang.
Kenapa delegation lebih stabil untuk list dinamis?
Listener menempel di induk yang permanen, sehingga tombol baru otomatis tertangani tanpa pasang ulang.