Tangan pengembang mengklik mouse di samping laptop yang menampilkan editor kode JavaScript.

Saat tombol hapus di daftar belanja bereaksi dua kali setelah halaman dimuat ulang sebagian, biasanya ada event handler yang terpasang lebih dari sekali. Masalah ini muncul karena setiap pemanggilan .on() menambah handler baru tanpa menghapus yang lama. Perbaikan paling cepat adalah memakai .off() sebelum memasang ulang, atau menggantinya dengan .one() bila handler hanya perlu jalan sekali.

Mengapa handler bisa menumpuk

jQuery tidak memeriksa apakah handler dengan fungsi yang sama sudah terpasang. Setiap .on('click', fn) menambahkan entri baru ke daftar handler elemen. Akibatnya, satu klik memicu dua, tiga, atau lebih pemanggilan sekaligus. Gejalanya mudah dikenali: permintaan AJAX terkirim berulang, animasi berjalan tumpang tindih, atau modal terbuka beberapa lapis.

Penyebab paling umum adalah kode yang dijalankan ulang setelah konten dimuat lewat AJAX atau setelah partial view diganti. Handler lama masih menempel pada elemen induk yang tidak ikut dihapus, lalu handler baru ditambahkan di atasnya. Pada kasus lain, handler dipasang di dalam callback yang sendirinya dipanggil berkali-kali.

Memakai .off() sebelum .on()

Pola paling langsung adalah melepas handler lama sebelum memasang yang baru. jQuery menyediakan .off() yang menerima tipe event, selector, atau referensi fungsi.

function pasangTombol() {
  $('#tombol-simpan')
    .off('click.simpan')
    .on('click.simpan', function () {
      simpanData();
    });
}

pasangTombol(); // aman dipanggil berulang

Perhatikan label .simpan di belakang tipe event. Itu adalah namespace, penanda yang membuat .off() hanya melepas handler milik kode ini. Tanpa namespace, .off('click') akan mencabut semua handler click pada elemen tersebut, termasuk yang dipasang plugin lain.

Namespace juga berguna saat elemen yang sama dipakai oleh beberapa modul. Modul keranjang memakai click.keranjang, modul favorit memakai click.favorit. Keduanya bisa membersihkan handler masing-masing tanpa saling mengganggu.

Kapan .off() tidak cukup

Bila handler dipasang pada elemen induk dengan event delegation, melepasnya dari elemen induk akan mematikan handler untuk semua anak. Dalam situasi ini, gunakan namespace pada handler delegasi dan pastikan selector-nya spesifik. Alternatif lain adalah memindahkan pemasangan handler ke satu fungsi inisialisasi yang hanya dipanggil sekali saat halaman siap.

Memakai .one() untuk aksi sekali jalan

Beberapa aksi memang hanya masuk akal dijalankan sekali, misalnya menutup banner promosi atau mengirim formulir pendaftaran. Untuk kebutuhan ini, .one() lebih tepat daripada .on() yang dibersihkan manual.

$('#tutup-banner').one('click', function () {
  $(this).closest('.banner').slideUp();
});

Handler otomatis dilepas setelah eksekusi pertama, jadi klik kedua tidak memicu apa pun. Ini menghindari penumpukan handler bila elemen yang sama dipakai ulang di kemudian hari, misalnya setelah banner ditampilkan lagi lewat animasi.

Mendeteksi handler yang menumpuk

jQuery menyimpan daftar handler di data internal elemen. Untuk pemeriksaan cepat di konsol browser, kamu bisa memanggil $._data(elemen, 'events') dan melihat berapa banyak fungsi terdaftar untuk tipe tertentu. API ini tidak didokumentasikan untuk penggunaan produksi, tetapi berguna saat menelusuri bug.

  • Buka DevTools, pilih elemen yang bermasalah lewat $('#id-elemen').
  • Jalankan $._data($('#id-elemen')[0], 'events').
  • Periksa jumlah entri pada tipe click. Lebih dari satu berarti ada penumpukan.

Jika daftar handler terus bertambah setiap kali fungsi tertentu dipanggil, itu tanda bahwa pemasangan handler perlu dipindahkan ke luar fungsi tersebut. Cara manipulasi DOM yang sering memicu masalah ini dibahas lebih rinci di panduan manipulasi DOM dengan jQuery.

Pola inisialisasi yang aman

Struktur kode yang rapi mencegah sebagian besar event ganda. Pisahkan pemasangan handler dari logika render, lalu panggil pemasangan hanya sekali saat dokumen siap.

  1. Letakkan semua pemasangan handler di dalam satu fungsi initEvents().
  2. Panggil initEvents() sekali di $(document).ready().
  3. Untuk konten yang dimuat dinamis, gunakan event delegation pada induk yang stabil.
  4. Beri namespace pada setiap handler agar bisa dilepas secara terpisah.

Event delegation membuat handler cukup dipasang sekali di elemen induk, meskipun anak-anaknya berganti-ganti. Detail perilaku delegation dan bubbling dijelaskan di artikel event jQuery: click, change, dan submit. Pola ini juga mengurangi jumlah handler yang perlu dibersihkan ketika konten diperbarui lewat AJAX.

Membersihkan handler saat elemen dihapus

jQuery menghapus data dan handler pada elemen yang dihapus lewat metode seperti .remove(). Namun, elemen yang dihapus dengan cara lain, misalnya dengan mengubah innerHTML secara langsung, bisa meninggalkan referensi yang tidak terpakai. Jika aplikasi sering mengganti blok konten besar, biasakan memakai .empty() atau .remove() agar handler ikut dibersihkan.

Event ganda terjadi karena pemasangan handler yang berulang, bukan karena bug di jQuery. Namespace pada .off() dan .one() memberi kendali penuh atas siklus hidup handler. Dengan struktur inisialisasi yang jelas, satu klik akan selalu memicu satu aksi.

Pertanyaan yang Sering Diajukan

Apakah .off() menghapus semua handler pada elemen?

Hanya jika dipanggil tanpa argumen, misalnya .off(). Jika kamu menyebut tipe event seperti .off('click'), semua handler click pada elemen itu dilepas. Untuk melepas handler tertentu saja, sertakan namespace atau referensi fungsi.

Apakah .one() bisa dipakai dengan event delegation?

Bisa. jQuery mendukung .one('click', selector, handler) dengan selector delegasi. Handler akan berjalan sekali untuk klik pertama yang cocok, lalu dilepas untuk elemen tersebut.

Mengapa handler tetap ganda setelah memakai .off()?

Periksa apakah .off() dipanggil pada elemen yang sama dengan tempat handler dipasang. Jika handler dipasang lewat delegation pada induk, .off() juga harus diarahkan ke induk dengan selector yang sama.