Bab 1: Merespons Event
⏱ 6 menit bacaApa Itu Event?
Bayangin kamu lagi di warung kopi. Kamu angkat tangan, dan mas barista langsung nanya "Pesan apa, mas?" Nah, angkat tangan itu adalah event (kejadian), dan respons barista itu adalah event handler (penangan kejadian).
Di dunia web, event itu semua interaksi yang dilakukan user: klik tombol, ketik di input, geser mouse, scroll halaman, submit form, dan masih banyak lagi. React punya cara khusus buat "mendengarkan" event-event ini dan meresponsnya.
Kenapa ini penting? Karena tanpa event, website kamu cuma pajangan statis. Kayak brosur. Nggak bisa diklik, nggak bisa diisi, nggak bisa apa-apa. Event bikin website kamu hidup.
Event Handler: Fungsi yang Merespons
Event handler itu cuma fungsi biasa. Nggak ada yang spesial. Yang bikin dia jadi "event handler" adalah kapan dia dipanggil, yaitu ketika event tertentu terjadi.
function TombolSapa() {
// Ini event handler — fungsi biasa yang dipanggil saat tombol diklik
function handleClick() {
alert('Halo! Selamat datang di warung kopi digital!');
}
return (
<button onClick={handleClick}>
Sapa Saya
</button>
);
}Coba sendiri: Edit kode di bawah dan lihat hasilnya langsung!
Perhatiin beberapa hal:
- Fungsi
handleClickdidefinisikan di dalam komponen — ini pola yang umum banget - Namanya diawali
handle— ini konvensi (kebiasaan), bukan aturan wajib. Tapi bikin kode lebih gampang dibaca - Di
onClick, kita passing fungsinya, BUKAN memanggilnya —onClick={handleClick}bukanonClick={handleClick()}
Poin ketiga itu krusial banget. Mari kita bahas lebih dalam.
Passing Fungsi vs Memanggil Fungsi
Ini salah satu sumber kebingungan terbesar buat pemula. Perhatiin bedanya:
// ✅ BENAR — passing referensi fungsi
<button onClick={handleClick}>Klik</button>
// ❌ SALAH — memanggil fungsi langsung
<button onClick={handleClick()}>Klik</button>Analoginya gini: bayangin kamu ngasih nomor HP ke temen.
onClick={handleClick}= "Nih nomor HP gue, telepon kalau butuh" (kasih nomor, belum ditelepon)onClick={handleClick()}= Kamu langsung telepon sendiri sekarang juga, terus kasih hasilnya ke temen (fungsi langsung jalan saat render!)
Kalau pakai handleClick() dengan tanda kurung, fungsi itu bakal langsung dipanggil saat komponen di-render, bukan saat diklik. Ini bug yang sering banget terjadi.
function TombolBahaya() {
function handleClick() {
console.log('Diklik!');
}
return (
// ❌ Ini bakal langsung jalan saat render, bukan saat klik!
<button onClick={handleClick()}>Klik Saya</button>
);
}Inline Handler vs Named Function
Kamu bisa nulis event handler langsung di JSX (inline) atau bikin fungsi terpisah (named). Dua-duanya valid.
function ContohHandler() {
// Cara 1: Named function (fungsi bernama)
function handleKlik() {
alert('Cara 1: Named function');
}
return (
<div>
{/* Cara 1: Pakai fungsi bernama */}
<button onClick={handleKlik}>Tombol 1</button>
{/* Cara 2: Inline arrow function */}
<button onClick={() => alert('Cara 2: Inline')}>Tombol 2</button>
{/* Cara 3: Inline tapi multi-line */}
<button onClick={() => {
console.log('Langkah 1');
console.log('Langkah 2');
alert('Cara 3: Multi-line inline');
}}>Tombol 3</button>
</div>
);
}Kapan pakai yang mana?
- Named function: Kalau logiknya panjang (lebih dari 1-2 baris), atau kalau handler-nya dipakai di beberapa tempat
- Inline: Kalau logiknya pendek dan simpel, atau kalau perlu passing argumen tambahan
function DaftarMenu() {
// Named function cocok karena logiknya lumayan panjang
function handlePesan(namaMenu) {
console.log(`Memesan: ${namaMenu}`);
alert(`${namaMenu} sedang disiapkan!`);
}
return (
<div>
{/* Inline arrow function buat passing argumen */}
<button onClick={() => handlePesan('Kopi Susu')}>Pesan Kopi Susu</button>
<button onClick={() => handlePesan('Es Teh')}>Pesan Es Teh</button>
<button onClick={() => handlePesan('Roti Bakar')}>Pesan Roti Bakar</button>
</div>
);
}Jenis-Jenis Event di React
React punya banyak event yang bisa kamu tangkap. Berikut yang paling sering dipakai:
onClick — Klik
function TombolKlik() {
return (
<button onClick={() => console.log('Tombol diklik!')}>
Klik Saya
</button>
);
}onChange — Perubahan Input
function InputNama() {
function handleChange(e) {
console.log('Nilai baru:', e.target.value);
}
return (
<input
type="text"
onChange={handleChange}
placeholder="Ketik nama kamu"
/>
);
}onSubmit — Submit Form
function FormLogin() {
function handleSubmit(e) {
e.preventDefault(); // Cegah halaman refresh!
console.log('Form disubmit!');
}
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}onMouseEnter & onMouseLeave — Hover
function KartuProduk() {
return (
<div
onMouseEnter={() => console.log('Mouse masuk!')}
onMouseLeave={() => console.log('Mouse keluar!')}
style={{ padding: '20px', border: '1px solid gray' }}
>
Hover di sini!
</div>
);
}onKeyDown — Tekan Keyboard
function InputDenganEnter() {
function handleKeyDown(e) {
if (e.key === 'Enter') {
console.log('Enter ditekan! Kirim pesan...');
}
}
return (
<input
type="text"
onKeyDown={handleKeyDown}
placeholder="Tekan Enter untuk kirim"
/>
);
}Event Object (e) — Informasi Tentang Event
Setiap event handler otomatis menerima satu parameter: event object. Biasanya disingkat e atau event. Object ini berisi informasi detail tentang event yang terjadi.
Analoginya: kalau event itu "ada orang datang ke warung", maka event object itu "laporan lengkap" — siapa yang datang, jam berapa, dari mana, naik apa.
function InfoEvent() {
function handleClick(e) {
// e.target = elemen yang diklik
console.log('Elemen yang diklik:', e.target);
// e.type = jenis event
console.log('Jenis event:', e.type);
// e.clientX, e.clientY = posisi mouse saat klik
console.log('Posisi klik:', e.clientX, e.clientY);
}
return (
<button onClick={handleClick}>
Klik dan cek console
</button>
);
}e.target vs e.currentTarget
Ini sering bikin bingung:
e.target= elemen yang benar-benar diklik usere.currentTarget= elemen yang punya event handler
function ContohTarget() {
function handleClick(e) {
console.log('target:', e.target); // bisa <span> atau <button>
console.log('currentTarget:', e.currentTarget); // selalu <button>
}
return (
<button onClick={handleClick}>
<span>Teks di dalam tombol</span>
</button>
);
}Kalau user klik teks "Teks di dalam tombol", e.target adalah <span>, tapi e.currentTarget tetap <button> karena handler-nya ada di button.
Preventing Default Behavior (e.preventDefault)
Beberapa elemen HTML punya perilaku bawaan (default behavior):
<form>— otomatis refresh halaman saat submit<a>— otomatis pindah ke URL href<input type="checkbox">— otomatis toggle centang
Kadang kita mau mencegah perilaku bawaan ini. Misalnya, kita mau handle form submit sendiri tanpa refresh halaman.
function FormPendaftaran() {
function handleSubmit(e) {
// Cegah halaman refresh!
e.preventDefault();
// Sekarang kita bisa handle sendiri
console.log('Form diproses tanpa refresh halaman');
// Kirim data ke server pakai fetch/axios
}
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Nama lengkap" />
<input type="email" placeholder="Email" />
<button type="submit">Daftar</button>
</form>
);
}function LinkKustom() {
function handleClick(e) {
// Cegah navigasi default
e.preventDefault();
// Handle navigasi sendiri (misalnya pakai React Router)
console.log('Navigasi dicegah, handle sendiri');
}
return (
<a href="https://google.com" onClick={handleClick}>
Klik link ini (nggak akan pindah halaman)
</a>
);
}Analoginya: e.preventDefault() itu kayak bilang ke browser "Eh, jangan lakuin yang biasa kamu lakuin ya. Biar gue yang handle." Kayak kamu bilang ke ojol "Jangan lewat jalan biasa, gue tau jalan pintas."
Event Propagation (Bubbling) & stopPropagation
Ini konsep yang agak tricky tapi penting banget. Event propagation artinya event "naik" dari elemen anak ke elemen induk.
Bayangin gini: kamu di dalam mall, teriak "DISKON!" Suara kamu nggak cuma kedengeran di toko tempat kamu berdiri, tapi juga kedengeran di lorong mall, lantai itu, bahkan mungkin lantai atas. Event di browser juga gitu — dia "naik" (bubble up) dari elemen terdalam ke elemen terluar.
function ContohPropagation() {
return (
<div onClick={() => console.log('DIV diklik!')}>
<button onClick={() => console.log('BUTTON diklik!')}>
Klik Saya
</button>
</div>
);
}Kalau kamu klik tombol, yang terjadi:
'BUTTON diklik!'muncul di console'DIV diklik!'JUGA muncul di console!
Kenapa? Karena event "naik" dari button ke div. Ini namanya bubbling.
Menghentikan Propagation
Kadang kita nggak mau event naik ke parent. Gunakan e.stopPropagation():
function ContohStopPropagation() {
return (
<div onClick={() => console.log('DIV diklik!')}>
<button onClick={(e) => {
e.stopPropagation(); // Stop! Jangan naik ke div!
console.log('BUTTON diklik!');
}}>
Klik Saya
</button>
</div>
);
}Sekarang kalau klik tombol, cuma 'BUTTON diklik!' yang muncul. Event-nya berhenti di button, nggak naik ke div.
Contoh Dunia Nyata: Modal/Popup
function Modal() {
function handleBackdropClick() {
console.log('Backdrop diklik — tutup modal');
}
function handleModalClick(e) {
// Jangan biarkan klik di dalam modal menutup modal!
e.stopPropagation();
}
return (
// Backdrop (latar belakang gelap)
<div className="backdrop" onClick={handleBackdropClick}>
{/* Modal content */}
<div className="modal" onClick={handleModalClick}>
<h2>Konfirmasi Pesanan</h2>
<p>Yakin mau pesan Kopi Susu x2?</p>
<button onClick={() => console.log('Dikonfirmasi!')}>
Ya, Pesan!
</button>
</div>
</div>
);
}Tanpa stopPropagation, klik di dalam modal juga bakal trigger handleBackdropClick dan menutup modal. Nggak lucu kan?
Passing Event Handler Sebagai Props
Komponen parent sering perlu "ngasih tau" komponen child apa yang harus dilakukan saat event terjadi. Caranya: passing event handler sebagai props.
Analoginya: kamu (parent) ngasih instruksi ke adik kamu (child): "Kalau ada tamu datang, bilang 'Papa lagi nggak di rumah'." Kamu kasih instruksinya, adik kamu yang jalanin.
// Komponen child — terima handler dari parent
function Tombol({ onClick, teks }) {
return (
<button onClick={onClick}>
{teks}
</button>
);
}
// Komponen parent — definisikan handler, passing ke child
function Toolbar() {
function handleSimpan() {
console.log('Data disimpan!');
}
function handleHapus() {
console.log('Data dihapus!');
}
return (
<div>
<Tombol onClick={handleSimpan} teks="Simpan" />
<Tombol onClick={handleHapus} teks="Hapus" />
</div>
);
}Perhatiin: komponen Tombol nggak tau (dan nggak perlu tau) apa yang terjadi saat diklik. Dia cuma tau "kalau diklik, jalanin fungsi yang dikasih parent." Ini bikin komponen jadi reusable (bisa dipakai ulang).
Penamaan Props untuk Event Handler
Konvensi penamaan:
- Props event handler biasanya diawali
on—onClick,onSubmit,onPesan - Fungsi handler-nya diawali
handle—handleClick,handleSubmit,handlePesan
// Komponen reusable dengan nama event kustom
function TombolPesan({ onPesan, namaMenu }) {
return (
<button onClick={() => onPesan(namaMenu)}>
Pesan {namaMenu}
</button>
);
}
function MenuWarung() {
function handlePesan(menu) {
alert(`${menu} sedang disiapkan! Tunggu 5 menit ya.`);
}
return (
<div>
<h2>Menu Warung Kopi</h2>
<TombolPesan onPesan={handlePesan} namaMenu="Kopi Susu" />
<TombolPesan onPesan={handlePesan} namaMenu="Es Teh Manis" />
<TombolPesan onPesan={handlePesan} namaMenu="Roti Bakar" />
</div>
);
}Contoh Lengkap: Form Pemesanan
Mari gabungkan semua konsep di atas dalam satu contoh yang realistis:
function FormPemesanan() {
function handleSubmit(e) {
// Cegah refresh halaman
e.preventDefault();
// Ambil data dari form
const formData = new FormData(e.target);
const nama = formData.get('nama');
const menu = formData.get('menu');
const jumlah = formData.get('jumlah');
console.log(`Pesanan: ${jumlah}x ${menu} untuk ${nama}`);
alert(`Pesanan diterima! ${jumlah}x ${menu} untuk ${nama}`);
}
function handleReset(e) {
// Stop propagation biar nggak trigger event di parent
e.stopPropagation();
if (confirm('Yakin mau reset form?')) {
e.target.closest('form').reset();
}
}
function handleInputChange(e) {
console.log(`Field ${e.target.name} berubah: ${e.target.value}`);
}
return (
<div onClick={() => console.log('Container diklik')}>
<h2>Form Pemesanan Warung</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Nama:</label>
<input
type="text"
name="nama"
onChange={handleInputChange}
placeholder="Nama pemesan"
/>
</div>
<div>
<label>Menu:</label>
<select name="menu" onChange={handleInputChange}>
<option value="kopi-susu">Kopi Susu</option>
<option value="es-teh">Es Teh Manis</option>
<option value="jus-alpukat">Jus Alpukat</option>
</select>
</div>
<div>
<label>Jumlah:</label>
<input
type="number"
name="jumlah"
min="1"
max="10"
defaultValue="1"
onChange={handleInputChange}
/>
</div>
<button type="submit">Pesan Sekarang</button>
<button type="button" onClick={handleReset}>Reset</button>
</form>
</div>
);
}⚠️ Jebakan
Jebakan 1: Memanggil fungsi alih-alih passing referensi
// ❌ SALAH — fungsi langsung dipanggil saat render
<button onClick={handleClick()}>Klik</button>
// ✅ BENAR — passing referensi fungsi
<button onClick={handleClick}>Klik</button>
// ✅ BENAR — kalau butuh argumen, bungkus dengan arrow function
<button onClick={() => handleClick('argumen')}>Klik</button>Jebakan 2: Lupa e.preventDefault() di form
// ❌ Form akan refresh halaman!
function FormBuruk() {
function handleSubmit() {
console.log('Ini mungkin nggak sempat muncul karena halaman refresh');
}
return <form onSubmit={handleSubmit}>...</form>;
}
// ✅ Cegah refresh dengan preventDefault
function FormBaik() {
function handleSubmit(e) {
e.preventDefault();
console.log('Sekarang aman, halaman nggak refresh');
}
return <form onSubmit={handleSubmit}>...</form>;
}Jebakan 3: Bingung event bubbling
// ❌ Klik tombol juga trigger handler di div
function Masalah() {
return (
<div onClick={() => alert('DIV!')}>
<button onClick={() => alert('BUTTON!')}>Klik</button>
</div>
);
}
// ✅ Pakai stopPropagation kalau nggak mau bubble
function Solusi() {
return (
<div onClick={() => alert('DIV!')}>
<button onClick={(e) => {
e.stopPropagation();
alert('BUTTON!');
}}>Klik</button>
</div>
);
}Jebakan 4: Typo nama event
// ❌ SALAH — huruf kecil semua (ini HTML biasa, bukan React)
<button onclick={handleClick}>Klik</button>
// ❌ SALAH — pakai dash
<button on-click={handleClick}>Klik</button>
// ✅ BENAR — camelCase
<button onClick={handleClick}>Klik</button>React pakai camelCase untuk nama event: onClick, onChange, onSubmit, onMouseEnter, dll. Bukan onclick atau on-click.
Jebakan 5: Return value dari handler
// ❌ Ini nggak akan mencegah default behavior
function handleSubmit(e) {
return false; // Ini nggak ngefek di React!
}
// ✅ Harus pakai preventDefault()
function handleSubmit(e) {
e.preventDefault(); // Ini yang benar
}Di jQuery/vanilla JS lama, return false bisa mencegah default. Di React, HARUS pakai e.preventDefault().
🏋️ Challenge
Challenge 1: Tombol Counter Sederhana
Buat komponen yang punya tombol. Setiap kali diklik, tampilkan alert berapa kali tombol sudah diklik.
💡 Hint
Kamu bisa pakai variabel di luar handler untuk tracking jumlah klik. (Catatan: ini belum ideal, nanti kita belajar useState yang lebih proper.)
✅ Solusi
function TombolCounter() {
let jumlahKlik = 0;
function handleClick() {
jumlahKlik++;
alert(`Tombol sudah diklik ${jumlahKlik} kali!`);
}
return (
<button onClick={handleClick}>
Klik Saya!
</button>
);
}Catatan: Ini belum sempurna karena variabel jumlahKlik akan reset setiap re-render. Di bab selanjutnya kita belajar useState yang menyelesaikan masalah ini.
Challenge 2: Form dengan Validasi
Buat form dengan input nama dan email. Saat disubmit:
- Cegah refresh halaman
- Validasi bahwa kedua field tidak kosong
- Kalau kosong, tampilkan alert error
- Kalau terisi, tampilkan alert sukses dengan data yang diisi
💡 Hint
Gunakan e.preventDefault() dan e.target untuk mengakses form. Kamu bisa pakai new FormData(e.target) untuk ambil nilai input.
✅ Solusi
function FormValidasi() {
function handleSubmit(e) {
// Cegah refresh
e.preventDefault();
// Ambil data form
const formData = new FormData(e.target);
const nama = formData.get('nama');
const email = formData.get('email');
// Validasi
if (!nama || !email) {
alert('Error: Semua field harus diisi!');
return;
}
// Sukses
alert(`Pendaftaran berhasil!\nNama: ${nama}\nEmail: ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Nama: </label>
<input type="text" name="nama" placeholder="Nama lengkap" />
</div>
<div>
<label>Email: </label>
<input type="email" name="email" placeholder="email@contoh.com" />
</div>
<button type="submit">Daftar</button>
</form>
);
}Challenge 3: Tombol dengan Stop Propagation
Buat layout dengan:
- Sebuah div (card) yang kalau diklik menampilkan "Card diklik"
- Di dalam card ada tombol "Hapus" yang kalau diklik menampilkan "Item dihapus"
- Pastikan klik tombol Hapus TIDAK memicu alert "Card diklik"
💡 Hint
Gunakan e.stopPropagation() di handler tombol Hapus untuk mencegah event naik ke div parent.
✅ Solusi
function CardDenganTombol() {
function handleCardClick() {
alert('Card diklik — mungkin buka detail');
}
function handleHapus(e) {
// Cegah event naik ke card
e.stopPropagation();
alert('Item dihapus!');
}
return (
<div
onClick={handleCardClick}
style={{
padding: '20px',
border: '1px solid #ccc',
borderRadius: '8px',
cursor: 'pointer'
}}
>
<h3>Kopi Susu Gula Aren</h3>
<p>Rp 25.000</p>
<button onClick={handleHapus}>
Hapus dari Keranjang
</button>
</div>
);
}Tanpa e.stopPropagation(), klik tombol "Hapus" akan menampilkan DUA alert: "Item dihapus!" dan "Card diklik". Dengan stopPropagation, cuma "Item dihapus!" yang muncul.
Ringkasan
- Event = kejadian yang dilakukan user (klik, ketik, submit, dll)
- Event handler = fungsi yang merespons event
- Passing handler:
onClick={handleClick}(tanpa tanda kurung!) - Event object (e) berisi info detail tentang event
e.preventDefault()= cegah perilaku bawaan browsere.stopPropagation()= cegah event naik ke parent- Event handler bisa di-passing sebagai props dari parent ke child
- Konvensi: props pakai
onprefix, handler pakaihandleprefix
Di bab selanjutnya, kita bakal belajar tentang State — cara komponen "mengingat" sesuatu. Karena sekarang, kalau kamu perhatiin, komponen kita belum bisa menyimpan data yang berubah. Tombol counter di Challenge 1 tadi nggak bisa update tampilan di layar. State adalah solusinya!
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.