Bab 1: Merespons Event

6 menit baca

Apa 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.

jsx
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:

  1. Fungsi handleClick didefinisikan di dalam komponen — ini pola yang umum banget
  2. Namanya diawali handle — ini konvensi (kebiasaan), bukan aturan wajib. Tapi bikin kode lebih gampang dibaca
  3. Di onClick, kita passing fungsinya, BUKAN memanggilnyaonClick={handleClick} bukan onClick={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:

jsx
// ✅ 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.

jsx
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.

jsx
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
jsx
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

jsx
function TombolKlik() {
  return (
    <button onClick={() => console.log('Tombol diklik!')}>
      Klik Saya
    </button>
  );
}

onChange — Perubahan Input

jsx
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

jsx
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

jsx
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

jsx
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.

jsx
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 user
  • e.currentTarget = elemen yang punya event handler
jsx
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.

jsx
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>
  );
}
jsx
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.

jsx
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:

  1. 'BUTTON diklik!' muncul di console
  2. '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():

jsx
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

jsx
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.

jsx
// 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 ononClick, onSubmit, onPesan
  • Fungsi handler-nya diawali handlehandleClick, handleSubmit, handlePesan
jsx
// 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:

jsx
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

jsx
// ❌ 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

jsx
// ❌ 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

jsx
// ❌ 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

jsx
// ❌ 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

jsx
// ❌ 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
jsx
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
jsx
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
jsx
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 browser
  • e.stopPropagation() = cegah event naik ke parent
  • Event handler bisa di-passing sebagai props dari parent ke child
  • Konvensi: props pakai on prefix, handler pakai handle prefix

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.