D
P
0

WordPress & PHP

Tombol “Mati” Cuma di Homepage: Handler Klik Global di File JS yang Tak Di-enqueue di Halaman Itu

28 Juni 2026·4 menit baca
Tombol “Mati” Cuma di Homepage: Handler Klik Global di File JS yang Tak Di-enqueue di Halaman Itu

Tombol favorit berbentuk hati di sebuah multi-listing site yang saya bangun berfungsi sempurna di mana-mana. Klik di halaman pencarian, jalan. Klik di halaman detail listing, jalan. Klik di halaman kategori, jalan. Tapi di homepage? Mati total. Tidak ada animasi, tidak ada request, tidak ada apa-apa. Tombolnya kelihatan, bisa di-hover, tapi klik tidak menghasilkan reaksi sedikit pun.

Markup-nya identik di semua halaman, karena tombol itu di-render dari satu partial yang sama:

<button class="favorite-btn" data-listing-id="412" aria-pressed="false">
  <svg class="icon-heart">...</svg>
</button>

Dan handler-nya pakai event delegation di document, jadi seharusnya elemen yang muncul dari mana pun tetap kena. Begini kira-kira bentuknya:

document.addEventListener('click', function (e) {
  const btn = e.target.closest('.favorite-btn');
  if (!btn) return;
 
  const listingId = btn.dataset.listingId;
  toggleFavorite(listingId, btn);
});

Kode ini benar. Tidak ada bug logika di dalamnya. Tapi di homepage dia seolah-olah tidak ada. Reflek pertama saya salah arah: saya curiga ada elemen overlay transparan yang menutupi tombol, atau ada CSS pointer-events: none yang nyasar. Saya buang waktu cukup lama di situ sebelum sadar saya mendebug hal yang salah.

Kenapa ini terjadi

Yang menyelamatkan saya cuma satu hal sederhana: buka DevTools di homepage, masuk ke tab Console, lalu Network. Saya cari nama file JS yang berisi handler tadi. Ternyata file-nya search.js, dan di homepage file itu tidak ada sama sekali di tab Network. Tidak 404, bukan gagal load. Memang tidak pernah diminta oleh browser.

Begitu lihat itu, semuanya langsung masuk akal. Handler delegasi itu tinggal di search.js, dan search.js cuma di-wp_enqueue_script di halaman pencarian, di balik guard bergaya is_page:

function listing_enqueue_assets() {
    // BUG: search.js cuma dimuat di halaman pencarian,
    // padahal handler favorit yang ada di dalamnya dipakai di SELURUH situs.
    if ( is_page( 'search' ) ) {
        wp_enqueue_script(
            'listing-search',
            get_template_directory_uri() . '/assets/js/search.js',
            array(),
            '1.0',
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'listing_enqueue_assets' );

Di halaman lain yang masih "jalan", file ini ternyata kebetulan ikut termuat lewat jalur lain, atau halaman-halaman itu sendiri adalah turunan dari konteks search. Tapi homepage berdiri sendiri, di luar guard is_page('search'), jadi search.js tidak pernah dimuat di sana.

Dan ini inti dari kesalahpahamannya: event delegation memang membuat satu handler menangani semua elemen, bahkan yang muncul belakangan. Tapi delegation sama sekali tidak ada gunanya kalau script-nya sendiri tidak pernah dieksekusi di halaman itu. document.addEventListener itu baru jalan kalau file yang memuatnya benar-benar di-load. Tidak ada script, tidak ada listener. Tidak ada listener, tombol mati. Kelihatan seperti bug khusus homepage, padahal ini murni bug scope enqueue.

Perbaikannya

Solusinya bukan menambah guard untuk homepage. Itu cuma menambal satu lubang sambil meninggalkan lubang lain menganga. Handler favorit ini sifatnya global, dipakai di seluruh situs, jadi tempatnya bukan di file yang scope-nya halaman pencarian.

Saya pindahkan handler global itu ke file yang memang di-enqueue di semua halaman tanpa syarat, misalnya main.js:

function listing_enqueue_assets() {
    // Global: dimuat di SEMUA halaman, tanpa kondisi.
    wp_enqueue_script(
        'listing-main',
        get_template_directory_uri() . '/assets/js/main.js',
        array(),
        '1.0',
        true
    );
 
    // Spesifik halaman: hanya yang benar-benar dipakai di sana.
    if ( is_page( 'search' ) ) {
        wp_enqueue_script(
            'listing-search',
            get_template_directory_uri() . '/assets/js/search.js',
            array(),
            '1.0',
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'listing_enqueue_assets' );

Handler click delegasi untuk .favorite-btn pindah ke main.js, sementara search.js cukup menyimpan hal-hal yang benar-benar khusus pencarian seperti filter dan autocomplete. Setelah itu saya buka tab Network di tiap jenis halaman dan memastikan main.js selalu muncul, lalu klik tombol hati di homepage. Langsung hidup.

Sambil di sana, saya juga melakukan satu hal yang seharusnya saya lakukan dari awal: audit setiap kondisi wp_enqueue_script di tema. Saya petakan tiap file ke pertanyaan sederhana, "perilaku di dalamnya ini global atau khusus halaman?" Beberapa script ternyata terlalu sempit scope-nya, beberapa lagi malah dimuat di mana-mana padahal cuma dipakai di satu halaman. Membereskan pemetaan itu mencegah versi bug yang sama muncul lagi di tempat lain.

Pelajaran

Sebuah delegated event handler cuma bekerja di halaman tempat script-nya benar-benar dimuat. Itu saja inti ceritanya. Begitu ada interaksi yang jalan di sebagian halaman tapi mati di halaman lain, jangan langsung membongkar logika handler-nya. Cek dulu apakah file JS-nya betul-betul ada di tab Network di halaman yang gagal itu. Kalau file-nya tidak ada di sana, masalahnya bukan di handler, tapi di scope enqueue.

Kesalahan yang membuat saya buang waktu adalah berasumsi bahwa kode yang sama berarti perilaku yang sama. Kode handler-nya memang identik di semua halaman, tapi yang membuatnya hidup bukan kode itu sendiri melainkan apakah file-nya di-load. Di WordPress, "di-load atau tidak" itu keputusan yang dibuat oleh wp_enqueue_script beserta semua kondisinya. Jadi sebelum mendebug logika, debug dulu pemuatannya. Tab Network sering kali menjawab pertanyaan lebih cepat daripada satu jam membaca ulang JavaScript yang sebetulnya sudah benar.