kodingup.camp

Beranda/React Mastery: Styling, Pola Komponen, Data & Optimasi

Styling Komponen Langsung

Modul 1 dari 135 menit bacaInline Component Styling

Isi modul

    Kenapa ini penting

    Komponen yang benar secara logika tapi tampil berantakan tetap terasa "rusak" di mata pengguna. Di React ada beberapa cara memberi style, dan masing-masing punya tempatnya. Kalau kamu tidak paham bedanya, biasanya terjadi dua masalah: style bocor ke komponen lain (tombol di halaman A ikut berubah karena class .button di halaman B), atau kode JSX penuh objek style panjang yang sulit dibaca. Modul ini membahas empat cara paling dasar: style object (inline style), file CSS biasa, CSS Modules, dan conditional className. Keempatnya bawaan Vite, tanpa library tambahan.

    Konsep inti

    1. Style object (atribut style)

    Di HTML kamu menulis style="color: red; font-size: 16px". Di JSX, atribut style tidak menerima string, melainkan object JavaScript. Nama property ditulis dengan camelCase (fontSize, bukan font-size), dan nilainya berupa string atau angka. Angka tanpa satuan otomatis dianggap px untuk property yang butuh satuan (misalnya padding: 8 menjadi 8px).

    Karena ini object JavaScript, kamu bebas menghitung nilainya dari state atau props, misalnya lebar progress bar width: persen + '%'. Inilah kekuatan utama inline style: nilai yang dinamis per-render. Kelemahannya: tidak bisa memakai :hover, @media, atau pseudo-element ::before, dan kalau objeknya besar, JSX jadi penuh.

    Analogi: inline style itu seperti menempel sticky note langsung di satu barang. Cepat, tapi kalau kamu punya 50 barang yang mau ditandai sama, lebih baik pakai label resmi (class CSS).

    2. File CSS biasa

    Vite mengizinkan kamu import './Card.css' langsung di file komponen. CSS-nya akan dimasukkan ke halaman. Atribut class di HTML ditulis className di JSX karena class adalah kata kunci di JavaScript.

    Hal penting yang sering tidak disadari pemula: CSS biasa itu global. Walaupun kamu import Card.css di dalam Card.jsx, aturan .title di dalamnya berlaku untuk semua elemen bernama class title di seluruh aplikasi. Komponen hanya "membawa" file itu, bukan "mengurungnya".

    3. CSS Modules

    CSS Modules menyelesaikan masalah bocor tadi. Cukup beri nama file berakhiran .module.css, misalnya Card.module.css. Saat di-import, kamu mendapat object yang memetakan nama class asli ke nama class unik buatan Vite (misalnya title menjadi _title_1x2ab_3). Jadi .title di Card.module.css tidak akan pernah bentrok dengan .title di file lain.

    Cara Scope Bisa :hover / @media Cocok untuk
    Style object Satu elemen Tidak Nilai dinamis (lebar, warna dari data)
    CSS biasa Global Ya Reset, font, variabel global
    CSS Modules Per file (lokal) Ya Style komponen sehari-hari
    Utility class (Tailwind) Per elemen lewat class Ya Dibahas di modul berikutnya

    4. Conditional className

    Sering kali tampilan berubah tergantung state: tombol aktif, input error, item selesai dicoret. Caranya: susun string className berdasarkan kondisi. Bisa memakai template literal, operator ternary, atau trik array + filter(Boolean).join(' ') supaya tidak ada spasi atau kata false nyasar. Banyak proyek memakai library kecil clsx untuk ini, tapi kamu perlu paham dulu versi manualnya.

    Memilih cara yang tepat: alur keputusan

    Saat akan memberi style, tanyakan tiga hal berurutan. Pertama, apakah style ini berlaku untuk seluruh aplikasi (font dasar, warna latar body, reset margin)? Kalau ya, taruh di index.css yang di-import sekali di main.jsx. Kedua, apakah nilainya berubah-ubah mengikuti data saat aplikasi berjalan, misalnya lebar bar sesuai persentase, warna label sesuai kategori dari server, atau posisi elemen yang diseret? Kalau ya, pakai style object untuk property yang dinamis itu saja. Ketiga, selain dua kasus tadi, tulis di CSS Modules milik komponen tersebut.

    Ada juga trik yang menggabungkan dua dunia: CSS custom property (variabel CSS). Kamu bisa mengirim nilai dinamis lewat style object, misalnya style={{ '--warna': warna }}, lalu memakainya di CSS Modules dengan background: var(--warna). Dengan cara ini :hover dan media query tetap bisa dipakai, sementara nilainya tetap datang dari props.

    Soal penamaan di CSS Modules: karena nama class otomatis dibuat unik, kamu boleh memakai nama pendek dan jelas seperti .item, .judul, .aktif. Tidak perlu awalan panjang ala BEM (.task-item__title--active). Jika nama class memakai tanda minus, aksesnya dengan kurung siku: styles['judul-besar']. Agar lebih nyaman, banyak tim memilih camelCase (.judulBesar) sehingga bisa ditulis styles.judulBesar.

    Contoh kode

    Pertama, style object yang dinamis: progress bar.

    // ProgressBar.jsx
    export default function ProgressBar({ persen }) {
      // object style dihitung ulang tiap render, jadi ikut perubahan props
      const wadah = {
        background: '#e5e7eb',
        borderRadius: 8, // angka → 8px
        height: 12,
        overflow: 'hidden',
      };
      const isi = {
        width: `${persen}%`,
        height: '100%',
        background: persen >= 100 ? '#16a34a' : '#2563eb', // hijau kalau sudah penuh
        transition: 'width 0.3s',
      };
    
      return (
        <div style={wadah} role="progressbar" aria-valuenow={persen}>
          <div style={isi} />
        </div>
      );
    }
    
    // <ProgressBar persen={40} />  // → bar biru terisi 40%
    // <ProgressBar persen={100} /> // → bar hijau penuh
    

    Kedua, CSS Modules plus conditional className untuk kartu tugas.

    /* TaskItem.module.css */
    .item {
      display: flex;
      gap: 8px;
      padding: 12px;
      border: 1px solid #d1d5db;
      border-radius: 8px;
    }
    .item:hover {
      background: #f9fafb;
    }
    .selesai {
      opacity: 0.6;
      text-decoration: line-through;
    }
    .penting {
      border-color: #dc2626;
    }
    
    // TaskItem.jsx
    import styles from './TaskItem.module.css';
    
    export default function TaskItem({ judul, selesai, penting, onToggle }) {
      // susun class: buang yang bernilai false, lalu gabung dengan spasi
      const kelas = [
        styles.item,
        selesai && styles.selesai,
        penting && styles.penting,
      ]
        .filter(Boolean)
        .join(' ');
    
      return (
        <label className={kelas}>
          <input type="checkbox" checked={selesai} onChange={onToggle} />
          {judul}
        </label>
      );
    }
    
    // Kalau selesai=true dan penting=false, class yang muncul di DOM kira-kira:
    // → "_item_k3j1a_1 _selesai_k3j1a_12"
    

    Ketiga, file CSS global untuk hal-hal yang memang global.

    // main.jsx
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import './index.css'; // reset & font global, sengaja berlaku ke semua
    import App from './App.jsx';
    
    createRoot(document.getElementById('root')).render(
      <StrictMode>
        <App />
      </StrictMode>
    );
    

    Kesalahan umum

    1. Menulis style="color: red" sebagai string. React akan error karena style wajib object. Benar: style={{ color: 'red' }}. Kurung kurawal pertama berarti "ekspresi JavaScript", kurung kedua adalah object-nya.
    2. Memakai font-size atau background-color di style object. Tanda minus tidak valid sebagai nama property tanpa kutip. Pakai camelCase: fontSize, backgroundColor.
    3. Mengira CSS biasa otomatis lokal. import './Card.css' tetap global. Kalau ingin aman dari bentrok, ganti jadi Card.module.css dan pakai styles.namaClass.
    4. Menghasilkan class "false" atau "undefined". Contoh className={'btn ' + (aktif && 'aktif')} akan menghasilkan "btn false" saat aktif bernilai false. Pakai ternary aktif ? 'aktif' : '' atau pola filter(Boolean).
    5. Menaruh semua style di inline object. Kamu kehilangan :hover, media query, dan JSX jadi sulit dibaca. Simpan inline style untuk nilai yang benar-benar dinamis saja.

    Latihan

    1. Mudah, Buat komponen Badge yang menerima props warna dan teks, lalu menampilkan teks dengan background sesuai warna memakai style object.
    2. Sedang, Buat komponen Tombol dengan CSS Modules yang punya varian primary dan danger (props varian) serta state disabled. Varian dan disabled harus mengubah class.
    3. Menantang, Buat komponen Avatar yang ukurannya (props ukuran dalam px) diatur lewat style object, tapi efek hover (bayangan) dan bentuk bulat diatur lewat CSS Modules. Jelaskan dalam komentar kenapa pembagiannya begitu.
    Lihat solusi
    // 1. Badge.jsx
    export function Badge({ warna = '#2563eb', teks }) {
      return (
        <span style={{ background: warna, color: 'white', padding: '2px 8px', borderRadius: 999 }}>
          {teks}
        </span>
      );
    }
    // <Badge warna="#16a34a" teks="Lunas" /> → label hijau bertuliskan Lunas
    
    /* 2. Tombol.module.css */
    .btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; color: white; }
    .primary { background: #2563eb; }
    .danger { background: #dc2626; }
    .disabled { opacity: 0.5; cursor: not-allowed; }
    
    // 2. Tombol.jsx
    import styles from './Tombol.module.css';
    
    export function Tombol({ varian = 'primary', disabled = false, children, ...rest }) {
      const kelas = [styles.btn, styles[varian], disabled && styles.disabled]
        .filter(Boolean)
        .join(' ');
      return (
        <button className={kelas} disabled={disabled} {...rest}>
          {children}
        </button>
      );
    }
    
    /* 3. Avatar.module.css */
    .avatar { border-radius: 50%; object-fit: cover; transition: box-shadow 0.2s; }
    .avatar:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.3); }
    
    // 3. Avatar.jsx
    import styles from './Avatar.module.css';
    
    export function Avatar({ src, ukuran = 48, alt }) {
      // ukuran berasal dari props (dinamis) → style object
      // hover & bentuk bulat statis dan butuh :hover → CSS Modules
      return (
        <img className={styles.avatar} src={src} alt={alt} style={{ width: ukuran, height: ukuran }} />
      );
    }
    

    Ringkasan

    Kuis pemahaman

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