Custom Event: Komunikasi Antar Komponen Tanpa Coupling
Membuat event sendiri dengan CustomEvent dan dispatchEvent agar komponen bisa saling memberi kabar tanpa saling kenal.
Kenapa Custom Event Ada?
Bayangkan aplikasi kasir warung: komponen keranjang menambah barang, dan badge di navbar harus update jumlahnya. Cara naifnya: fungsi keranjang langsung memanggil fungsi navbar. Masalahnya, sekarang keranjang "kenal" navbar, keduanya saling terkait (coupling). Tambah satu pendengar lagi (misal panel rekap), kamu harus edit kode keranjang lagi.
Custom event menyelesaikan ini dengan pola publish-subscribe: keranjang cuma berteriak "keranjang berubah!" ke udara lewat dispatchEvent. Siapa pun yang peduli (navbar, rekap, notifikasi) tinggal pasang telinga lewat addEventListener. Keranjang tidak perlu tahu siapa pendengarnya, dan pendengar baru bisa ditambah tanpa menyentuh kode keranjang.
Contoh 1: Dasar CustomEvent
// 1. Daftarkan pendengar (di mana saja, kapan saja)
document.addEventListener("user-login", (e) => {
console.log("Halo, " + e.detail.nama + "!");
console.log("Waktu login:", e.detail.waktu);
});
// 2. Kirim event dari tempat lain, lengkap dengan data
const eventLogin = new CustomEvent("user-login", {
detail: { nama: "Budi", waktu: new Date().toLocaleTimeString() },
});
document.dispatchEvent(eventLogin);
// Output: Halo, Budi! / Waktu login: ...Kuncinya ada di properti detail: wadah resmi untuk membawa data apa pun (object, array, string) bersama event. Jangan tempel data di properti sembarangan, detail adalah konvensinya.
Contoh 2: Keranjang Memberi Kabar ke Navbar (Realistis)
<button id="tambah">Tambah Kopi (Rp8.000)</button>
<span id="badge">Keranjang: 0 barang</span>
<ul id="rekap"></ul>const keranjang = [];
// Komponen 1: tombol tambah, TIDAK TAHU soal badge/rekap
document.getElementById("tambah").addEventListener("click", () => {
keranjang.push({ nama: "Kopi", harga: 8000 });
// Berteriak ke udara: "keranjang berubah!"
document.dispatchEvent(new CustomEvent("keranjang-berubah", {
detail: { jumlah: keranjang.length, total: keranjang.reduce((s, b) => s + b.harga, 0) },
}));
});
// Komponen 2: badge navbar, cuma mendengarkan
document.addEventListener("keranjang-berubah", (e) => {
document.getElementById("badge").textContent =
"Keranjang: " + e.detail.jumlah + " barang";
});
// Komponen 3: panel rekap, pendengar baru TANPA ubah kode komponen 1
document.addEventListener("keranjang-berubah", (e) => {
const li = document.createElement("li");
li.textContent = "Update: " + e.detail.jumlah + " barang, total Rp" + e.detail.total;
document.getElementById("rekap").appendChild(li);
});Mau tambah komponen ke-4 (misal toast notifikasi)? Tinggal addEventListener lagi. Nol perubahan di kode pengirim.
Jebakan Umum
1. Lupa bubbles: true saat butuh delegation. Secara default custom event TIDAK bubble, ia cuma sampai di target tempat ia di-dispatch. Kalau kamu dispatch di elemen anak tapi listener-nya di parent/document, eventnya tidak akan sampai kecuali:
// SALAH: listener di document tidak akan menerima ini
tombol.dispatchEvent(new CustomEvent("selesai"));
// BENAR: aktifkan bubbles
tombol.dispatchEvent(new CustomEvent("selesai", { bubbles: true }));(Aman dispatch langsung di document seperti contoh di atas, tapi untuk komponen yang rapi, dispatch di elemen komponennya sendiri plus bubbles: true.)
2. Nama event generik bertabrakan. Nama seperti "change" atau "update" bisa bentrok dengan event bawaan atau library lain. Pakai namespace: "keranjang:berubah" atau "warung-user-login".
3. Mengirim object mutable di detail lalu mengubahnya lagi. Listener menerima referensi yang sama. Kalau pengirim mengubah object setelah dispatch, listener yang memproses async bisa baca data yang sudah berubah. Kirim salinan ({...data}) kalau datanya akan dimutasi lagi.
Ringkasan
new CustomEvent(nama, { detail }) plus dispatchEvent adalah cara komponen berkomunikasi tanpa saling mengenal. Ingat tiga hal: data lewat detail, aktifkan bubbles: true kalau listener-nya di ancestor, dan beri nama yang spesifik. Pola ini adalah fondasi arsitektur event-driven yang dipakai framework besar.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
Tantangan
Sistem Notifikasi Warung
Di dunia nyata, aplikasi besar terdiri dari puluhan komponen yang harus saling memberi kabar: stok menipis, pesanan selesai, user logout. Kalau setiap komponen memanggil komponen lain langsung, kode jadi kusut dan susah diubah. Custom event adalah solusi standarnya. Buat sistem notifikasi: fungsi jualBarang(nama) mengurangi stok di object stok, lalu dispatch event stok-berubah dengan detail { nama, sisa }. Dua pendengar independen: satu update tampilan stok, satu menampilkan peringatan jika sisa < 5.
<!doctype html> <html> <head> <meta charset="utf-8" /> </head> <body> <h1>Halo JS</h1> <p>Buka console preview untuk melihat output.</p> <script src="index.js"></script> </body> </html>