Bab 4: JavaScript dalam JSX dengan Kurung Kurawal

4 menit baca

Jendela ke Dunia JavaScript

Di bab sebelumnya, kamu udah belajar nulis JSX. Sekarang pertanyaannya: gimana caranya bikin JSX itu dinamis? Masa iya semua teks di-hardcode?

Bayangin JSX itu kayak template undangan nikahan. Strukturnya sama (ada nama mempelai, tanggal, tempat), tapi isinya beda-beda tergantung siapa yang nikah. Nah, kurung kurawal {} itu kayak "kolom kosong" di template yang bisa diisi data berbeda.

jsx
// Tanpa kurung kurawal - statis, membosankan
function Undangan() {
  return <h1>Selamat Datang, Tamu</h1>;
}

// Dengan kurung kurawal - dinamis!
function Undangan() {
  const nama = "Pak Budi";
  return <h1>Selamat Datang, {nama}</h1>;
}

Kurung kurawal {} adalah jendela dari dunia JSX ke dunia JavaScript. Apapun yang kamu taruh di dalam {}, React akan menjalankannya sebagai kode JavaScript dan menampilkan hasilnya.

Di Mana Kurung Kurawal Bisa Dipakai?

Kurung kurawal bisa dipakai di dua tempat dalam JSX:

1. Sebagai Konten (Isi Teks)

jsx
function Sapaan() {
  const nama = "Siti";
  const umur = 25;
  
  return (
    <div>
      {/* Sebagai teks di antara tag pembuka dan penutup */}
      <h1>Halo, {nama}!</h1>
      <p>Umur: {umur} tahun</p>
      <p>Tahun lahir: {2024 - umur}</p>
    </div>
  );
}

2. Sebagai Nilai Atribut

jsx
function Avatar() {
  const urlFoto = "https://example.com/foto-siti.jpg";
  const altText = "Foto profil Siti";
  const ukuran = 150;
  
  return (
    <img 
      src={urlFoto}           // Atribut src pakai variabel
      alt={altText}           // Atribut alt pakai variabel
      width={ukuran}          // Atribut width pakai variabel
      height={ukuran}         // Bisa pakai variabel yang sama
      className="avatar"      // String biasa nggak perlu {}
    />
  );
}

Perhatiin: Kalau nilai atribut itu string yang udah fix (nggak dinamis), kamu tetap pakai tanda kutip biasa tanpa kurung kurawal. className="avatar" itu oke, nggak perlu className={"avatar"} (meskipun keduanya valid).

Di Mana Kurung Kurawal NGGAK Bisa Dipakai?

jsx
// ❌ SALAH - nggak bisa pakai {} untuk nama tag
const tag = "h1";
return <{tag}>Halo</{tag}>;  // Error!

// ❌ SALAH - nggak bisa pakai {} untuk nama atribut
const atribut = "className";
return <div {atribut}="box">Halo</div>;  // Error!

Kurung kurawal cuma bisa di posisi konten (antara tag pembuka dan penutup) dan posisi nilai atribut (setelah tanda =).

Apa yang Bisa Dimasukkan ke Kurung Kurawal?

Aturan emas: Apapun yang merupakan ekspresi JavaScript bisa masuk ke {}.

Apa Itu Ekspresi?

Ekspresi adalah potongan kode yang menghasilkan nilai. Gampangnya: kalau bisa ditaruh di sebelah kanan tanda =, itu ekspresi.

jsx
// Semua ini EKSPRESI (menghasilkan nilai):
const a = 5 + 3;              // 8
const b = nama.toUpperCase(); // "SITI"
const c = umur >= 18;         // true
const d = items.length;       // angka
const e = kondisi ? "ya" : "tidak"; // string

Contoh Ekspresi yang Valid di JSX:

jsx
function ContohEkspresi() {
  const nama = "Ahmad";
  const items = ["Nasi", "Ayam", "Teh"];
  const harga = 25000;
  const diskon = 0.1;
  
  return (
    <div>
      {/* Variabel */}
      <p>{nama}</p>
      
      {/* Operasi matematika */}
      <p>Total: Rp {harga - (harga * diskon)}</p>
      
      {/* Pemanggilan method */}
      <p>{nama.toLowerCase()}</p>
      <p>Jumlah item: {items.length}</p>
      
      {/* Ternary operator */}
      <p>{harga > 20000 ? 'Mahal' : 'Murah'}</p>
      
      {/* Template literal */}
      <p>{`Halo, ${nama}! Kamu punya ${items.length} item.`}</p>
      
      {/* Pemanggilan fungsi */}
      <p>{Math.round(harga * diskon)}</p>
      <p>{new Date().getFullYear()}</p>
    </div>
  );
}

Apa yang BUKAN Ekspresi (Nggak Bisa Masuk {})?

Statement (pernyataan) nggak bisa masuk ke kurung kurawal. Statement itu instruksi yang nggak menghasilkan nilai.

jsx
// ❌ SALAH - ini semua statement, bukan ekspresi
function Salah() {
  return (
    <div>
      {/* if/else BUKAN ekspresi */}
      {if (true) { return "halo" }}  // Error!
      
      {/* for loop BUKAN ekspresi */}
      {for (let i = 0; i < 5; i++) { }}  // Error!
      
      {/* deklarasi variabel BUKAN ekspresi */}
      {const x = 5}  // Error!
      
      {/* switch BUKAN ekspresi */}
      {switch(nilai) { case 1: break; }}  // Error!
    </div>
  );
}

Cara gampang bedain: Coba taruh di console.log(...). Kalau masuk akal, itu ekspresi. Kalau aneh, itu statement.

javascript
console.log(5 + 3);           // ✅ Masuk akal → ekspresi
console.log(nama.length);     // ✅ Masuk akal → ekspresi
console.log(if (true) {});    // ❌ Aneh → statement
console.log(for (;;) {});     // ❌ Aneh → statement

Objek dalam JSX: Kurung Kurawal Ganda {{}}

Ini yang sering bikin bingung pemula. Kadang kamu lihat {{}} di JSX dan mikir "apa ini sintaks khusus?" Bukan. Itu cuma objek JavaScript {} di dalam kurung kurawal JSX {}.

💡Info

Bayangin kurung kurawal JSX {} itu kayak jendela rumah. Kamu bisa lempar apapun lewat jendela itu. Nah, kalau yang kamu lempar itu sebuah kotak (objek {}), ya jadinya keliatan kayak jendela dengan kotak di dalamnya: {{}}.

jsx
// Ini bukan sintaks khusus!
// Ini: { (buka jendela JSX) { color: 'red' } (objek JS) } (tutup jendela JSX)

<div style={{ color: 'red', fontSize: '20px' }}>
  Teks merah
</div>

Mari kita breakdown:

jsx
// Langkah 1: Bikin objek style
const gayaSaya = { color: 'red', fontSize: '20px' };

// Langkah 2: Masukkan ke JSX pakai kurung kurawal
<div style={gayaSaya}>Teks merah</div>

// Atau langsung inline (jadinya kurung kurawal ganda):
<div style={{ color: 'red', fontSize: '20px' }}>Teks merah</div>

Inline Style di React

Di HTML biasa, style ditulis sebagai string: style="color: red; font-size: 20px". Di React/JSX, style itu objek JavaScript:

jsx
function KotakWarna() {
  return (
    <div style={{
      backgroundColor: 'lightblue',   // background-color → backgroundColor
      padding: '20px',                  // nilai string dengan unit
      borderRadius: '8px',             // border-radius → borderRadius
      fontSize: '16px',                // font-size → fontSize
      fontWeight: 'bold',              // font-weight → fontWeight
      textAlign: 'center',            // text-align → textAlign
      width: 200,                      // angka tanpa unit = pixel
      height: 100,                     // angka tanpa unit = pixel
    }}>
      Kotak Biru Muda
    </div>
  );
}

Aturan style di JSX:

  • Property CSS yang pakai dash (-) diubah ke camelCase
  • Nilai biasanya string (dengan unit): '20px', '1rem', '50%'
  • Kalau nilainya pixel, boleh pakai angka tanpa unit: width: 200 = width: '200px'

Style Dinamis

Kekuatan inline style di React: bisa dinamis berdasarkan data!

jsx
function BarProgress({ persen }) {
  return (
    <div style={{ 
      width: '100%', 
      backgroundColor: '#eee',
      borderRadius: '4px'
    }}>
      <div style={{
        width: `${persen}%`,                    // Dinamis!
        backgroundColor: persen > 70 ? 'green' : 'orange',  // Dinamis!
        height: '20px',
        borderRadius: '4px',
        transition: 'width 0.3s ease'
      }}>
        {persen}%
      </div>
    </div>
  );
}

Memasukkan Variabel dan Kalkulasi

Variabel Sederhana

jsx
function ProfilToko() {
  const namaToko = "Warung Makan Bu Sari";
  const rating = 4.8;
  const jumlahReview = 1250;
  const buka = true;
  
  return (
    <div className="profil-toko">
      <h1>{namaToko}</h1>
      <p>⭐ {rating} ({jumlahReview} review)</p>
      <p>Status: {buka ? '🟢 Buka' : '🔴 Tutup'}</p>
    </div>
  );
}

Kalkulasi dan Operasi

jsx
function RingkasanBelanja() {
  const items = [
    { nama: "Nasi Goreng", harga: 15000, qty: 2 },
    { nama: "Es Teh", harga: 5000, qty: 3 },
    { nama: "Kerupuk", harga: 3000, qty: 1 },
  ];
  
  // Kalkulasi di luar JSX
  const subtotal = items.reduce((acc, item) => acc + (item.harga * item.qty), 0);
  const pajak = subtotal * 0.1;
  const total = subtotal + pajak;
  
  return (
    <div className="ringkasan">
      <h2>Ringkasan Belanja</h2>
      
      {/* Kalkulasi langsung di JSX juga bisa */}
      <p>Jumlah item: {items.length}</p>
      <p>Subtotal: Rp {subtotal.toLocaleString('id-ID')}</p>
      <p>Pajak (10%): Rp {pajak.toLocaleString('id-ID')}</p>
      <hr />
      <p><strong>Total: Rp {total.toLocaleString('id-ID')}</strong></p>
      
      {/* Operasi string */}
      <p className="catatan">
        {items.length > 2 
          ? `Wah, kamu pesan ${items.length} jenis makanan!` 
          : 'Mau tambah pesanan lagi?'}
      </p>
    </div>
  );
}

Pemanggilan Fungsi

jsx
// Fungsi helper
function formatRupiah(angka) {
  return `Rp ${angka.toLocaleString('id-ID')}`;
}

function formatTanggal(tanggal) {
  return tanggal.toLocaleDateString('id-ID', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}

function Invoice() {
  const tanggalPesan = new Date();
  const totalBayar = 157500;
  
  return (
    <div className="invoice">
      <h2>Invoice Pembayaran</h2>
      {/* Panggil fungsi di dalam kurung kurawal */}
      <p>Tanggal: {formatTanggal(tanggalPesan)}</p>
      <p>Total: {formatRupiah(totalBayar)}</p>
      <p>Status: Lunas ✅</p>
    </div>
  );
}

Objek dan Array di JSX

Menampilkan Properti Objek

jsx
function KartuMahasiswa() {
  const mahasiswa = {
    nama: "Rina Wulandari",
    nim: "2024001234",
    jurusan: "Teknik Informatika",
    semester: 5,
    ipk: 3.75,
    foto: "https://example.com/rina.jpg"
  };
  
  return (
    <div className="kartu-mahasiswa">
      <img src={mahasiswa.foto} alt={`Foto ${mahasiswa.nama}`} />
      <h2>{mahasiswa.nama}</h2>
      <table>
        <tbody>
          <tr>
            <td>NIM</td>
            <td>{mahasiswa.nim}</td>
          </tr>
          <tr>
            <td>Jurusan</td>
            <td>{mahasiswa.jurusan}</td>
          </tr>
          <tr>
            <td>Semester</td>
            <td>{mahasiswa.semester}</td>
          </tr>
          <tr>
            <td>IPK</td>
            <td>{mahasiswa.ipk.toFixed(2)}</td>
          </tr>
        </tbody>
      </table>
    </div>
  );
}

Yang NGGAK Bisa Ditampilkan Langsung

React bisa menampilkan string, number, dan boolean (sebagai string). Tapi objek dan array nggak bisa ditampilkan langsung sebagai konten:

jsx
// ❌ Error: Objects are not valid as a React child
function Salah() {
  const user = { nama: "Budi", umur: 30 };
  return <p>{user}</p>;  // Error!
}

// ✅ Benar: akses properti spesifik
function Benar() {
  const user = { nama: "Budi", umur: 30 };
  return <p>{user.nama}, {user.umur} tahun</p>;
}
jsx
// Array bisa ditampilkan (React akan join elemennya)
function ContohArray() {
  const buah = ["Apel", "Mangga", "Jeruk"];
  
  return (
    <div>
      {/* Array of strings - React gabung jadi satu */}
      <p>{buah}</p>  {/* Output: ApelManggaJeruk (tanpa separator) */}
      
      {/* Lebih baik: join dengan separator */}
      <p>{buah.join(", ")}</p>  {/* Output: Apel, Mangga, Jeruk */}
    </div>
  );
}

Kurung Kurawal Bersarang (Nested)

Kadang kamu perlu kurung kurawal di dalam kurung kurawal. Ini valid dan sering terjadi:

jsx
function ProfilLengkap() {
  const data = {
    nama: "Joko Widodo",
    jabatan: "Presiden ke-7",
    periode: { mulai: 2014, selesai: 2024 }
  };
  
  return (
    <div style={{
      padding: '20px',
      border: `2px solid ${data.periode.selesai <= 2024 ? 'gray' : 'green'}`,
      borderRadius: '8px'
    }}>
      <h2>{data.nama}</h2>
      <p>{data.jabatan}</p>
      <p>Periode: {data.periode.mulai} - {data.periode.selesai}</p>
      <p>Durasi: {data.periode.selesai - data.periode.mulai} tahun</p>
    </div>
  );
}

Pola Umum: Variabel JSX

Kamu bisa menyimpan JSX di variabel, lalu pakai variabel itu di dalam JSX lain:

jsx
function HalamanProduk() {
  const sedangDiskon = true;
  const harga = 500000;
  
  // Simpan JSX di variabel
  let badgeDiskon;
  if (sedangDiskon) {
    badgeDiskon = <span className="badge">🔥 DISKON 20%!</span>;
  }
  
  // Konten yang berbeda berdasarkan kondisi
  const infoHarga = sedangDiskon 
    ? <p className="harga-diskon">Rp {(harga * 0.8).toLocaleString('id-ID')}</p>
    : <p className="harga">Rp {harga.toLocaleString('id-ID')}</p>;
  
  return (
    <div className="halaman-produk">
      <h1>Tas Ransel Premium</h1>
      {badgeDiskon}
      {infoHarga}
    </div>
  );
}

Contoh Komprehensif: Dashboard Cuaca

jsx
function DashboardCuaca() {
  // Data cuaca (biasanya dari API)
  const cuaca = {
    kota: "Jakarta",
    suhu: 32,
    kelembaban: 78,
    kondisi: "Berawan",
    angin: 15,
    lastUpdate: new Date(),
  };
  
  // Fungsi helper
  function getEmoji(kondisi) {
    const emojiMap = {
      'Cerah': '☀️',
      'Berawan': '⛅',
      'Hujan': '🌧️',
      'Badai': '⛈️',
    };
    return emojiMap[kondisi] || '🌤️';
  }
  
  function getSaranAktivitas(suhu, kondisi) {
    if (kondisi === 'Hujan' || kondisi === 'Badai') return 'Bawa payung!';
    if (suhu > 35) return 'Minum yang banyak, panas banget!';
    if (suhu > 30) return 'Pakai sunscreen kalau keluar rumah.';
    return 'Cuaca enak buat jalan-jalan!';
  }
  
  return (
    <div style={{
      padding: '24px',
      borderRadius: '12px',
      backgroundColor: cuaca.suhu > 30 ? '#fff3e0' : '#e3f2fd',
      maxWidth: '400px',
      fontFamily: 'sans-serif'
    }}>
      {/* Header */}
      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
        <h2 style={{ margin: 0 }}>📍 {cuaca.kota}</h2>
        <span style={{ fontSize: '2rem' }}>{getEmoji(cuaca.kondisi)}</span>
      </div>
      
      {/* Suhu utama */}
      <p style={{ fontSize: '3rem', margin: '10px 0', fontWeight: 'bold' }}>
        {cuaca.suhu}°C
      </p>
      
      {/* Detail */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
        <p>💨 Angin: {cuaca.angin} km/h</p>
        <p>💧 Kelembaban: {cuaca.kelembaban}%</p>
        <p>🌡️ Kondisi: {cuaca.kondisi}</p>
        <p>🕐 Update: {cuaca.lastUpdate.toLocaleTimeString('id-ID')}</p>
      </div>
      
      {/* Saran */}
      <p style={{
        marginTop: '16px',
        padding: '12px',
        backgroundColor: 'rgba(255,255,255,0.7)',
        borderRadius: '8px',
        fontStyle: 'italic'
      }}>
        💡 {getSaranAktivitas(cuaca.suhu, cuaca.kondisi)}
      </p>
    </div>
  );
}

⚠️ Jebakan

Jebakan 1: Lupa Kurung Kurawal

jsx
// ❌ Ini menampilkan teks literal "nama", bukan isi variabel
const nama = "Budi";
<h1>Halo, nama</h1>  // Output: "Halo, nama"

// ✅ Pakai kurung kurawal untuk akses variabel
<h1>Halo, {nama}</h1>  // Output: "Halo, Budi"

Jebakan 2: Pakai Kutip di Dalam Kurung Kurawal untuk Atribut

jsx
// ❌ Ini menampilkan string literal "{urlFoto}", bukan isi variabel
const urlFoto = "foto.jpg";
<img src="{urlFoto}" />  // src akan jadi string "{urlFoto}" 😱

// ✅ Pilih salah satu: kutip ATAU kurung kurawal
<img src={urlFoto} />      // Dinamis - pakai variabel
<img src="foto.jpg" />     // Statis - langsung string

Jebakan 3: Menampilkan Objek Langsung

jsx
// ❌ Error: Objects are not valid as a React child
const user = { nama: "Siti", kota: "Bandung" };
<p>{user}</p>

// ✅ Akses properti spesifik
<p>{user.nama} dari {user.kota}</p>

// ✅ Atau convert ke string dulu (untuk debugging)
<p>{JSON.stringify(user)}</p>

Jebakan 4: Statement di Dalam Kurung Kurawal

jsx
// ❌ if/else bukan ekspresi!
<p>{if (skor > 80) { "Bagus" } else { "Kurang" }}</p>

// ✅ Pakai ternary operator (ini ekspresi)
<p>{skor > 80 ? "Bagus" : "Kurang"}</p>

// ✅ Atau hitung di luar JSX
const penilaian = skor > 80 ? "Bagus" : "Kurang";
<p>{penilaian}</p>

Jebakan 5: Bingung {} vs {{}}

jsx
// ❌ Salah - style butuh objek, bukan string
<div style="color: red">Halo</div>

// ❌ Salah - cuma satu kurung kurawal, isinya bukan objek
<div style={color: 'red'}>Halo</div>

// ✅ Benar - kurung kurawal JSX + objek JavaScript
<div style={{ color: 'red' }}>Halo</div>

// Breakdown:
// {         → buka jendela JSX
//   {       → buka objek JavaScript
//     color: 'red'  → properti objek
//   }       → tutup objek JavaScript
// }         → tutup jendela JSX

Jebakan 6: Boolean, Null, dan Undefined

jsx
function ContohBoolean() {
  const tampilkan = true;
  const kosong = null;
  const belumAda = undefined;
  
  return (
    <div>
      {/* Boolean, null, undefined TIDAK ditampilkan (invisible) */}
      <p>{tampilkan}</p>   {/* Nggak ada output */}
      <p>{kosong}</p>      {/* Nggak ada output */}
      <p>{belumAda}</p>    {/* Nggak ada output */}
      
      {/* Kalau mau tampilkan sebagai teks, convert ke string */}
      <p>{String(tampilkan)}</p>  {/* Output: "true" */}
      <p>{`${kosong}`}</p>        {/* Output: "null" */}
      
      {/* TAPI angka 0 DITAMPILKAN! Ini sering jadi bug */}
      <p>{0}</p>  {/* Output: "0" */}
    </div>
  );
}

Ringkasan

KonsepContohPenjelasan
Variabel{nama}Tampilkan isi variabel
Kalkulasi{harga * 1.1}Hitung dan tampilkan hasil
Method{teks.toUpperCase()}Panggil method, tampilkan hasil
Fungsi{formatRupiah(harga)}Panggil fungsi, tampilkan hasil
Ternary{x ? 'ya' : 'tidak'}Kondisional inline
Objek (style)style={{ color: 'red' }}Objek di dalam kurung kurawal
Template literal{`Halo ${nama}`}String interpolation

🏋️ Challenge

Challenge 1: Kartu Cuaca Dinamis

Buat komponen KartuCuaca yang menampilkan:

  • Nama kota dari variabel
  • Suhu dalam Celsius DAN Fahrenheit (hitung konversinya: F = C × 9/5 + 32)
  • Emoji yang berubah berdasarkan suhu (< 20: 🥶, 20-30: 😊, > 30: 🥵)
  • Background color yang berubah berdasarkan suhu (dingin: biru, sedang: hijau, panas: merah)
💡 Hint
  • Rumus Fahrenheit: (celsius * 9/5) + 32
  • Pakai ternary bertingkat atau fungsi helper untuk emoji
  • Style dinamis: style={{ backgroundColor: suhu > 30 ? '#ffcdd2' : '#c8e6c9' }}
  • toFixed(1) untuk membatasi desimal
✅ Solusi
jsx
function KartuCuaca() {
  const kota = "Surabaya";
  const suhuCelsius = 33;
  
  // Kalkulasi
  const suhuFahrenheit = (suhuCelsius * 9/5) + 32;
  
  // Fungsi helper
  function getEmoji(suhu) {
    if (suhu < 20) return '🥶';
    if (suhu <= 30) return '😊';
    return '🥵';
  }
  
  function getWarna(suhu) {
    if (suhu < 20) return '#bbdefb';
    if (suhu <= 30) return '#c8e6c9';
    return '#ffcdd2';
  }
  
  return (
    <div style={{
      padding: '20px',
      borderRadius: '12px',
      backgroundColor: getWarna(suhuCelsius),
      textAlign: 'center',
      maxWidth: '300px'
    }}>
      <h2>📍 {kota}</h2>
      <p style={{ fontSize: '3rem', margin: '10px 0' }}>
        {getEmoji(suhuCelsius)}
      </p>
      <p style={{ fontSize: '2rem', fontWeight: 'bold' }}>
        {suhuCelsius}°C
      </p>
      <p style={{ color: '#666' }}>
        ({suhuFahrenheit.toFixed(1)}°F)
      </p>
      <p>
        {suhuCelsius > 30 
          ? 'Panas banget! Minum yang banyak ya.' 
          : suhuCelsius < 20 
            ? 'Dingin! Pakai jaket.' 
            : 'Cuaca enak nih!'}
      </p>
    </div>
  );
}

Challenge 2: Kalkulator Diskon

Buat komponen KalkulatorDiskon yang:

  • Punya variabel hargaAsli dan persenDiskon
  • Menampilkan harga asli (dicoret), harga setelah diskon, dan berapa yang dihemat
  • Format semua harga dalam Rupiah
  • Tampilkan badge "HEMAT!" kalau diskon lebih dari 20%
  • Warna harga diskon: hijau kalau diskon > 30%, oranye kalau 10-30%, hitam kalau < 10%
💡 Hint
  • Harga diskon: hargaAsli - (hargaAsli * persenDiskon / 100)
  • Hemat: hargaAsli * persenDiskon / 100
  • toLocaleString('id-ID') untuk format Rupiah
  • textDecoration: 'line-through' untuk coret harga
  • Ternary bertingkat untuk warna
✅ Solusi
jsx
function KalkulatorDiskon() {
  const hargaAsli = 750000;
  const persenDiskon = 25;
  
  // Kalkulasi
  const potongan = hargaAsli * persenDiskon / 100;
  const hargaFinal = hargaAsli - potongan;
  
  // Fungsi helper format Rupiah
  function formatRp(angka) {
    return `Rp ${angka.toLocaleString('id-ID')}`;
  }
  
  // Tentukan warna berdasarkan diskon
  function getWarnaDiskon(persen) {
    if (persen > 30) return 'green';
    if (persen >= 10) return 'orange';
    return 'black';
  }
  
  return (
    <div style={{ padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
      <h2>Kalkulator Diskon</h2>
      
      {/* Harga asli dicoret */}
      <p style={{ textDecoration: 'line-through', color: '#999' }}>
        {formatRp(hargaAsli)}
      </p>
      
      {/* Harga setelah diskon */}
      <p style={{ 
        fontSize: '1.5rem', 
        fontWeight: 'bold',
        color: getWarnaDiskon(persenDiskon)
      }}>
        {formatRp(hargaFinal)}
      </p>
      
      {/* Info hemat */}
      <p>Kamu hemat: {formatRp(potongan)} ({persenDiskon}%)</p>
      
      {/* Badge kalau diskon > 20% */}
      {persenDiskon > 20 ? (
        <span style={{
          backgroundColor: 'red',
          color: 'white',
          padding: '4px 8px',
          borderRadius: '4px',
          fontSize: '0.8rem'
        }}>
          🔥 HEMAT!
        </span>
      ) : null}
    </div>
  );
}

Challenge 3: Jam Digital

Buat komponen JamDigital yang menampilkan:

  • Waktu sekarang (jam:menit:detik) dari new Date()
  • Tanggal lengkap dalam Bahasa Indonesia
  • Sapaan yang berubah berdasarkan jam (Pagi/Siang/Sore/Malam)
  • Background gelap kalau malam (jam 18-06), terang kalau siang
💡 Hint
  • new Date().getHours(), .getMinutes(), .getSeconds()
  • .toLocaleDateString('id-ID', { weekday: 'long', ... }) untuk tanggal Indonesia
  • .toString().padStart(2, '0') untuk format 2 digit (01, 02, dst)
  • Pagi: 5-11, Siang: 11-15, Sore: 15-18, Malam: 18-5
✅ Solusi
jsx
function JamDigital() {
  const sekarang = new Date();
  const jam = sekarang.getHours();
  const menit = sekarang.getMinutes();
  const detik = sekarang.getSeconds();
  
  // Format 2 digit
  const jamStr = jam.toString().padStart(2, '0');
  const menitStr = menit.toString().padStart(2, '0');
  const detikStr = detik.toString().padStart(2, '0');
  
  // Tanggal lengkap
  const tanggal = sekarang.toLocaleDateString('id-ID', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
  
  // Sapaan berdasarkan jam
  function getSapaan(j) {
    if (j >= 5 && j < 11) return 'Selamat Pagi 🌅';
    if (j >= 11 && j < 15) return 'Selamat Siang ☀️';
    if (j >= 15 && j < 18) return 'Selamat Sore 🌇';
    return 'Selamat Malam 🌙';
  }
  
  // Cek apakah malam (untuk tema gelap)
  const isMalam = jam >= 18 || jam < 6;
  
  return (
    <div style={{
      padding: '30px',
      borderRadius: '16px',
      backgroundColor: isMalam ? '#1a1a2e' : '#f0f8ff',
      color: isMalam ? '#eee' : '#333',
      textAlign: 'center',
      fontFamily: 'monospace',
      maxWidth: '350px'
    }}>
      <p style={{ fontSize: '1.2rem', marginBottom: '8px' }}>
        {getSapaan(jam)}
      </p>
      <p style={{ fontSize: '3rem', fontWeight: 'bold', margin: '10px 0' }}>
        {jamStr}:{menitStr}:{detikStr}
      </p>
      <p style={{ fontSize: '1rem', color: isMalam ? '#aaa' : '#666' }}>
        {tanggal}
      </p>
    </div>
  );
}

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.