D
P
0

JavaScript

Satu Inline onclick stopPropagation Diam-diam Merusak Semua Delegated Handler

14 Juni 2026·3 menit baca
Satu Inline onclick stopPropagation Diam-diam Merusak Semua Delegated Handler

Bug ini termasuk yang paling licik, karena gejalanya adalah tidak ada gejala. Sebuah tombol favorit (ikon hati) di kartu produk diklik — dan tidak terjadi apa-apa. Tidak ada perubahan UI, tidak ada network request, tidak ada error di console. Tombolnya seperti hidup (ada hover state, kursor pointer), tapi mengkliknya seolah membicarakan ke ruang hampa.

Yang bikin makin membingungkan: navigasi kartunya juga tidak jalan, jadi sekilas seperti tombolnya "menangkap" klik dengan benar. Tapi tidak ada satu pun aksi yang dieksekusi.

Struktur HTML-nya begini — tombol hati bersarang di dalam <a> yang membungkus seluruh kartu:

<a href="/product/123" class="card">
  <!-- isi kartu -->
  <button
    class="favorite"
    data-id="123"
    onclick="event.preventDefault(); event.stopPropagation();"
  >

  </button>
</a>

onclick inline itu sengaja ditaruh untuk mencegah kartu navigasi saat hati diklik. Sementara itu, logika simpan favorit yang sebenarnya ditangani oleh delegated handler yang dipasang di document:

document.addEventListener("click", (e) => {
  const btn = e.target.closest(".favorite");
  if (!btn) return;
  e.preventDefault();
  e.stopPropagation();
  saveFavorite(btn.dataset.id); // ini yang tidak pernah jalan
});

Pola yang masuk akal, kan? Inline onclick menghentikan navigasi, handler global menyimpan favorit. Tapi saveFavorite() tidak pernah dipanggil.

Akar masalah: stopPropagation membunuh delegasi

Begini yang sebenarnya terjadi. Inline onclick berjalan di target phase — sebelum event sempat naik (bubble) ke leluhur. Saat handler inline memanggil event.stopPropagation(), ia menghentikan event tepat di situ. Event tidak pernah bubble sampai ke document. Karena delegated handler dipasang di document dan bergantung pada bubbling untuk menerima event, handler itu tidak pernah dipicu. Hasilnya: preventDefault() mencegah navigasi (itu kenapa kartu diam), tapi stopPropagation() juga memutus rantai ke handler global — jadi tidak ada favorit yang tersimpan.

Saya buktikan langsung di DevTools: hapus atribut onclick inline-nya, klik tombolnya lagi — dan langsung jalan. saveFavorite() terpanggil, request terkirim. Itu konfirmasi pasti bahwa si inline onclick lah biang keroknya.

Solusi: buang stopPropagation, sisakan preventDefault

Perbaikannya kecil. Cukup hapus event.stopPropagation() dari inline onclick, sisakan event.preventDefault() saja:

<button
  class="favorite"
  data-id="123"
  onclick="event.preventDefault();"
>

</button>

Kenapa ini aman? Karena delegated handler di document melakukan preventDefault() dan stopPropagation()-nya sendiri begitu ia menerima event. Jadi:

  • preventDefault() di inline tetap mencegah navigasi <a> di awal.
  • Event tetap bubble naik ke document (karena tidak ada lagi stopPropagation di inline).
  • Delegated handler menerima event, menjalankan logikanya, dan mem-preventDefault + stopPropagation di levelnya sendiri.

Navigasi tetap terblokir, dan favorit kini tersimpan.

Catatan penutup

  • Inline onclick berjalan di target phase, sebelum listener leluhur di bubble phase. Apa pun yang ia panggil terjadi lebih dulu — termasuk stopPropagation.
  • stopPropagation di inline membunuh handler delegasi yang dipasang di document. Event delegation bergantung penuh pada bubbling; hentikan bubbling, hentikan handler-nya.
  • Saat memakai event delegation, audit template untuk inline onclick yang memanggil stopPropagation. Itu kombinasi yang diam-diam saling meniadakan.
  • Bug "tanpa error, tanpa request" hampir selalu soal event flow. Tidak ada exception berarti kodenya tidak pernah sampai dijalankan — bukan gagal saat dijalankan.
  • Biarkan handler delegasi yang mengurus preventDefault/stopPropagation-nya sendiri. Jangan duplikasi di inline; cukup cegah default lokal kalau perlu.

Pelajaran intinya: kalau Anda memasang logika lewat delegasi di document, satu stopPropagation di mana pun di jalur bubble bisa mematikannya secara senyap. Kenali jalur event-nya sebelum menebak-nebak.