kodingup.camp

Beranda/React Mastery: Dasar, Rendering & Hooks

Rendering Antarmuka Dinamis

Modul 7 dari 124 menit bacaDynamic Interface Rendering

Isi modul

    Kenapa ini penting

    Antarmuka yang hidup selalu berubah sesuai keadaan: tombol "Login" berganti "Logout" setelah masuk, pesan error muncul saat form salah, spinner tampil saat data dimuat. Di sisi lain, perubahan itu dipicu oleh aksi pengguna: klik, ketik, pilih. Modul ini menggabungkan dua hal itu: conditional rendering (menampilkan elemen sesuai kondisi) dan event handling (merespons aksi pengguna), termasuk pola controlled input yang menjadi standar membuat form di React.

    Konsep inti

    Conditional rendering: empat pola utama

    Pola Kapan dipakai Contoh
    if + early return Seluruh tampilan berbeda total if (loading) return <Spinner />;
    Ternary ? : Pilih salah satu dari dua tampilan {isLogin ? <Logout /> : <Login />}
    && Tampilkan sesuatu atau tidak sama sekali {error && <p>{error}</p>}
    Variabel JSX Logika agak panjang, agar JSX tetap bersih let konten; if (...) konten = <A />;

    Early return artinya komponen langsung mengembalikan JSX lain di awal fungsi untuk kondisi khusus, sehingga sisa kode tidak perlu memikirkan kondisi tersebut. Analogi: satpam di pintu masuk. Kalau tidak punya tiket, langsung disuruh pulang, tidak perlu masuk ke dalam gedung.

    Komponen juga boleh mengembalikan null untuk tidak menampilkan apa pun.

    Jebakan angka 0 pada &&

    {jumlah && <p>Ada {jumlah} pesan</p>} terlihat benar, tapi saat jumlah bernilai 0, ekspresi 0 && ... menghasilkan 0, dan React menampilkan angka 0 di layar. Pastikan sisi kiri && selalu boolean: {jumlah > 0 && ...}.

    Event handling

    Event di React ditulis camelCase dan menerima fungsi, bukan string: <button onClick={handleKlik}>. Perhatikan perbedaannya:

    Handler menerima objek event (e). Yang sering dipakai: e.target.value (nilai input), e.target.checked (checkbox), e.preventDefault() (mencegah form reload halaman), dan e.stopPropagation() (mencegah event naik ke elemen induk).

    Event umum: onClick, onChange, onSubmit, onKeyDown, onFocus, onBlur, onMouseEnter.

    Controlled input

    Di HTML biasa, input menyimpan nilainya sendiri di DOM. Di React, pola yang dianjurkan adalah controlled input: nilai input dikendalikan state.

    1. value={state} membuat input selalu menampilkan isi state.
    2. onChange={e => setState(e.target.value)} memperbarui state setiap kali pengguna mengetik.

    Dengan begitu state adalah satu-satunya sumber kebenaran (single source of truth). Kamu bisa memvalidasi, memformat (misalnya otomatis huruf besar), atau mengosongkan input kapan saja hanya dengan mengubah state.

    Analogi: input biasa seperti buku catatan orang lain; kamu harus meminjamnya untuk tahu isinya. Controlled input seperti papan tulis di kelasmu sendiri; kamu selalu tahu isinya dan bisa menghapusnya kapan saja.

    Elemen Prop nilai Diambil dari event
    <input type="text"> value e.target.value
    <textarea> value e.target.value
    <select> value e.target.value
    <input type="checkbox"> checked e.target.checked

    Perhatian: jika kamu memberi value tanpa onChange, input menjadi read-only dan React memberi peringatan.

    Contoh kode

    // src/App.jsx, form login dengan semua pola conditional rendering dan controlled input
    import { useState } from 'react';
    
    function Dashboard({ nama, onLogout }) {
      return (
        <section>
          <h2>Selamat datang, {nama}!</h2>
          <button onClick={onLogout}>Logout</button>
        </section>
      );
    }
    
    function Notifikasi({ jumlah }) {
      // Kembalikan null = tidak menampilkan apa-apa
      if (jumlah === 0) return null;
      return <p>🔔 Kamu punya {jumlah} notifikasi baru</p>;
    }
    
    export default function App() {
      const [user, setUser] = useState(null);
      const [email, setEmail] = useState('');
      const [password, setPassword] = useState('');
      const [ingat, setIngat] = useState(false);
      const [error, setError] = useState('');
      const [jumlahNotif] = useState(0);
    
      function handleSubmit(e) {
        e.preventDefault(); // cegah halaman reload
        if (!email.includes('@')) {
          setError('Format email tidak valid');
          return;
        }
        if (password.length < 6) {
          setError('Password minimal 6 karakter');
          return;
        }
        setError('');
        setUser({ nama: email.split('@')[0], ingat });
      }
    
      // Early return: kalau sudah login, tampilkan dashboard saja
      if (user) {
        return (
          <>
            <Dashboard nama={user.nama} onLogout={() => setUser(null)} />
            <Notifikasi jumlah={jumlahNotif} />
            {/* ❌ {jumlahNotif && <p>...</p>} akan menampilkan "0" */}
            {jumlahNotif > 0 && <p>Cek kotak masuk.</p>}
          </>
        );
      }
    
      return (
        <form onSubmit={handleSubmit}>
          <h2>Login</h2>
          <input
            type="email"
            value={email}
            onChange={e => setEmail(e.target.value.trim())}
            placeholder="email@contoh.com"
          />
          <input
            type="password"
            value={password}
            onChange={e => setPassword(e.target.value)}
            placeholder="Password"
          />
          <label>
            <input type="checkbox" checked={ingat} onChange={e => setIngat(e.target.checked)} />
            Ingat saya
          </label>
    
          {/* && untuk tampil/tidak */}
          {error && <p style={{ color: 'crimson' }}>{error}</p>}
    
          {/* Ternary untuk memilih teks tombol */}
          <button type="submit" disabled={!email || !password}>
            {email && password ? 'Masuk' : 'Isi email & password'}
          </button>
    
          <p>Panjang password: {password.length} karakter</p>
        </form>
      );
    }
    // Ketik "budi" lalu submit → "Format email tidak valid"
    // Ketik "budi@mail.com" + "rahasia1" → "Selamat datang, budi!"
    

    Event lain dan objek event:

    import { useState } from 'react';
    
    export function KotakEvent() {
      const [log, setLog] = useState('');
      const [kota, setKota] = useState('jakarta');
    
      return (
        <div onClick={() => setLog('Div induk diklik')} style={{ padding: 16, background: '#f1f5f9' }}>
          <button
            onClick={e => {
              e.stopPropagation(); // tanpa ini, onClick div juga terpanggil
              setLog('Tombol diklik');
            }}
          >
            Klik aku
          </button>
    
          <input onKeyDown={e => e.key === 'Enter' && setLog('Enter ditekan: ' + e.target.value)} />
    
          <select value={kota} onChange={e => setKota(e.target.value)}>
            <option value="jakarta">Jakarta</option>
            <option value="surabaya">Surabaya</option>
            <option value="makassar">Makassar</option>
          </select>
    
          <p>{log} | Kota: {kota}</p>
        </div>
      );
    }
    

    Kesalahan umum

    1. Memanggil handler saat render. onClick={hapus(id)} langsung menjalankan hapus. Pakai onClick={() => hapus(id)}.
    2. Angka 0 muncul di layar karena &&. Ubah sisi kiri jadi boolean: {list.length > 0 && ...}.
    3. Lupa e.preventDefault() pada onSubmit. Halaman reload dan state hilang. Panggil di baris pertama handler submit.
    4. Memberi value tanpa onChange. Input tidak bisa diketik. Tambahkan onChange, atau pakai defaultValue kalau memang ingin uncontrolled.
    5. Memakai value untuk checkbox. Checkbox memakai checked dan e.target.checked.

    Latihan

    1. Mudah, Buat tombol "Tampilkan detail" yang menampilkan/menyembunyikan paragraf deskripsi produk memakai &&, dan teks tombol berganti memakai ternary.
    2. Sedang, Buat input "Nama" yang controlled. Tampilkan pesan "Nama terlalu pendek" (merah) jika panjangnya 1-2 karakter, "Nama valid ✓" (hijau) jika 3 ke atas, dan tidak ada pesan jika kosong.
    3. Menantang, Buat komponen StatusPesanan dengan prop status ('loading', 'error', 'kosong', 'sukses') yang memakai early return untuk setiap status, dan sebuah select di App untuk mengganti status.
    Lihat solusi

    1.

    import { useState } from 'react';
    
    export default function Detail() {
      const [buka, setBuka] = useState(false);
      return (
        <>
          <button onClick={() => setBuka(b => !b)}>{buka ? 'Sembunyikan detail' : 'Tampilkan detail'}</button>
          {buka && <p>Kopi arabika Gayo, sangrai medium, notes cokelat dan jeruk.</p>}
        </>
      );
    }
    

    2.

    import { useState } from 'react';
    
    export default function InputNama() {
      const [nama, setNama] = useState('');
      let pesan = null;
      if (nama.length > 0 && nama.length < 3) {
        pesan = <p style={{ color: 'crimson' }}>Nama terlalu pendek</p>;
      } else if (nama.length >= 3) {
        pesan = <p style={{ color: 'green' }}>Nama valid ✓</p>;
      }
    
      return (
        <>
          <input value={nama} onChange={e => setNama(e.target.value)} placeholder="Nama" />
          {pesan}
        </>
      );
    }
    

    3.

    import { useState } from 'react';
    
    function StatusPesanan({ status }) {
      if (status === 'loading') return <p>⏳ Memuat pesanan...</p>;
      if (status === 'error') return <p style={{ color: 'crimson' }}>Gagal memuat pesanan.</p>;
      if (status === 'kosong') return <p>Belum ada pesanan.</p>;
      return <p>✅ 3 pesanan ditemukan.</p>;
    }
    
    export default function App() {
      const [status, setStatus] = useState('loading');
      return (
        <>
          <select value={status} onChange={e => setStatus(e.target.value)}>
            <option value="loading">loading</option>
            <option value="error">error</option>
            <option value="kosong">kosong</option>
            <option value="sukses">sukses</option>
          </select>
          <StatusPesanan status={status} />
        </>
      );
    }
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.