Bab 1: Bereaksi Terhadap Input dengan State
⏱ 6 menit bacaPendahuluan: UI Itu Hidup, Bukan Patung
Coba bayangin kamu lagi pesan makanan di aplikasi ojol. Kamu ketik nama restoran, muncul daftar. Kamu klik satu menu, tombol "Tambah ke Keranjang" berubah jadi "✓ Ditambahkan". Kamu checkout, muncul loading spinner, terus muncul konfirmasi pesanan.
Semua perubahan visual itu... siapa yang ngatur? Di dunia React, jawabannya adalah state. Dan cara kita "berpikir" tentang perubahan UI ini sangat berbeda dari cara tradisional.
Di bab ini, kamu bakal belajar cara berpikir tentang UI secara deklaratif, bukan imperatif. Ini fondasi paling penting sebelum kamu bikin aplikasi React yang kompleks.
Imperatif vs Deklaratif: Dua Cara Berpikir
Cara Imperatif (kayak naik taksi biasa):
- "Pak, belok kiri di perempatan depan"
- "Terus lurus sampai lampu merah"
- "Belok kanan, masuk gang kedua"
- "Stop di rumah cat biru"
Kamu kasih instruksi langkah demi langkah. Kalau salah satu instruksi keliru, nyasar.
Cara Deklaratif (kayak pakai GPS di ojol):
- "Antar saya ke Jl. Merdeka No. 45"
Kamu cuma bilang tujuan akhirnya. GPS yang mikirin rutenya.
Dalam Kode
Imperatif (vanilla JavaScript):
// Kamu harus kasih tau SETIAP LANGKAH ke browser
const tombol = document.getElementById('kirim');
const form = document.getElementById('form-pesan');
const loading = document.getElementById('loading');
const sukses = document.getElementById('sukses');
tombol.addEventListener('click', function() {
// Langkah 1: Sembunyiin form
form.style.display = 'none';
// Langkah 2: Tampilin loading
loading.style.display = 'block';
// Langkah 3: Setelah selesai...
setTimeout(function() {
// Langkah 4: Sembunyiin loading
loading.style.display = 'none';
// Langkah 5: Tampilin pesan sukses
sukses.style.display = 'block';
}, 2000);
});Masalahnya? Kalau UI makin kompleks, kamu harus ingat semua elemen mana yang harus disembunyiin, ditampilin, diubah teksnya... Satu kelupaan, UI jadi kacau.
Deklaratif (React):
function FormPesan() {
const [status, setStatus] = useState('mengetik'); // 'mengetik' | 'mengirim' | 'sukses'
// Kamu cuma bilang: "Kalau statusnya X, tampilin Y"
if (status === 'sukses') {
return <h2>Pesan terkirim! ✅</h2>;
}
return (
<form onSubmit={handleSubmit}>
<textarea disabled={status === 'mengirim'} />
<button disabled={status === 'mengirim'}>
{status === 'mengirim' ? 'Mengirim...' : 'Kirim'}
</button>
</form>
);
}Coba sendiri: Edit kode di bawah dan lihat hasilnya langsung!
Lihat bedanya? Di React, kamu gak bilang "sembunyiin ini, tampilin itu". Kamu cuma bilang: "Kalau state-nya begini, UI-nya harusnya kayak gini." React yang urus sisanya.
Berpikir dalam "Visual States"
Lampu lalu lintas punya 3 state: 🔴 Merah, 🟡 Kuning, 🟢 Hijau.
Di setiap state, perilaku yang diharapkan JELAS:
- Merah → mobil berhenti
- Kuning → mobil siap-siap
- Hijau → mobil jalan
Lampu gak pernah merah DAN hijau bersamaan. Itu "impossible state".
UI kamu juga harus dipikirin kayak gini. Setiap "layar" atau "tampilan" yang mungkin muncul adalah satu visual state.
Langkah 1: Identifikasi Semua Visual State
Misal kamu bikin form pengiriman pesan. Apa aja state yang mungkin?
// State 1: KOSONG - user belum ngetik apa-apa
// State 2: MENGETIK - user lagi ngetik pesan
// State 3: MENGIRIM - pesan lagi dikirim ke server
// State 4: SUKSES - pesan berhasil terkirim
// State 5: ERROR - ada masalah saat kirimVisualisasiin kayak gini:
[KOSONG] → user ngetik → [MENGETIK] → user klik kirim → [MENGIRIM]
↓
berhasil? → [SUKSES]
gagal? → [ERROR] → user coba lagi → [MENGIRIM]
Langkah 2: Bikin "Mockup" untuk Setiap State
Ini teknik yang super berguna. Sebelum nulis logika, bikin dulu tampilan untuk SETIAP state:
function FormPesan() {
// Coba ganti-ganti nilai ini untuk lihat setiap state
const status = 'kosong'; // Ganti: 'kosong', 'mengetik', 'mengirim', 'sukses', 'error'
const error = null; // Ganti: null, 'Gagal mengirim pesan'
return (
<div>
<h2>Kirim Pesan ke CS</h2>
{/* State SUKSES */}
{status === 'sukses' && (
<div className="sukses">
<p>✅ Pesan kamu sudah terkirim!</p>
</div>
)}
{/* Form (tampil di semua state KECUALI sukses) */}
{status !== 'sukses' && (
<form>
<textarea
placeholder="Tulis pesan kamu..."
disabled={status === 'mengirim'}
/>
{/* Pesan error */}
{status === 'error' && (
<p className="error">❌ {error}</p>
)}
<button disabled={status === 'kosong' || status === 'mengirim'}>
{status === 'mengirim' ? '⏳ Mengirim...' : 'Kirim Pesan'}
</button>
</form>
)}
</div>
);
}Dengan cara ini, kamu bisa "preview" setiap state tanpa perlu logika yang rumit dulu. Ini namanya "storyboarding" UI kamu.
Menghubungkan Event Handler ke State
Oke, sekarang kita udah tau visual state-nya. Tapi gimana caranya berpindah dari satu state ke state lain?
Mesin ATM punya state: menunggu_kartu → memasukkan_pin → memilih_transaksi → memproses → selesai
Yang bikin berpindah state? Aksi dari user:
- Masukkin kartu → pindah ke
memasukkan_pin - Ketik PIN benar → pindah ke
memilih_transaksi - Pilih tarik tunai → pindah ke
memproses
Di React, "aksi dari user" ini ditangkap oleh event handler.
function FormPesan() {
const [status, setStatus] = useState('kosong');
const [pesan, setPesan] = useState('');
const [error, setError] = useState(null);
// EVENT: User ngetik di textarea
function handleKetik(e) {
setPesan(e.target.value);
// Kalau ada isinya, status jadi 'mengetik'
// Kalau kosong, status balik ke 'kosong'
setStatus(e.target.value.length > 0 ? 'mengetik' : 'kosong');
}
// EVENT: User klik tombol kirim
async function handleKirim(e) {
e.preventDefault();
setStatus('mengirim'); // Pindah ke state mengirim
try {
await kirimPesan(pesan); // Panggil API
setStatus('sukses'); // Berhasil!
} catch (err) {
setStatus('error'); // Gagal!
setError(err.message);
}
}
// ... render UI berdasarkan status
}Perhatiin polanya:
- Event terjadi (user ngetik, klik, dll)
- State berubah (via setState)
- UI otomatis update (React re-render)
Kamu gak perlu manually ubah DOM. Cukup ubah state, React yang urus tampilannya.
Mengurangi "Impossible States"
Bayangin formulir yang punya field "Status Pernikahan" dan "Nama Pasangan". Kalau status = "Belum Menikah", field nama pasangan harusnya gak bisa diisi. Tapi kalau kamu simpan keduanya secara terpisah tanpa aturan...
// ❌ BAHAYA: Bisa terjadi state yang mustahil
const [sedangMengirim, setSedangMengirim] = useState(false);
const [sudahTerkirim, setSudahTerkirim] = useState(false);
const [error, setError] = useState(null);Masalahnya? Secara teknis, sedangMengirim = true DAN sudahTerkirim = true bisa terjadi bersamaan. Itu kayak lampu merah dan hijau nyala bareng. Mustahil di dunia nyata, tapi kode kamu memungkinkannya.
Solusi: Gunakan Satu State dengan Nilai yang Jelas
// ✅ AMAN: Hanya satu status yang aktif pada satu waktu
const [status, setStatus] = useState('idle');
// Nilai yang mungkin: 'idle' | 'mengetik' | 'mengirim' | 'sukses' | 'error'Dengan cara ini, MUSTAHIL status jadi 'mengirim' DAN 'sukses' bersamaan. Satu variabel, satu nilai, satu kebenaran.
Kapan Pakai Boolean vs String/Enum?
// Boolean cocok untuk hal yang benar-benar on/off
const [modalTerbuka, setModalTerbuka] = useState(false);
// String/enum cocok untuk hal yang punya BANYAK kemungkinan
const [statusPesanan, setStatusPesanan] = useState('menunggu');
// 'menunggu' | 'diproses' | 'dikirim' | 'sampai' | 'dibatalkan'Aturan praktis: Kalau kamu punya lebih dari 2 state yang saling eksklusif (gak bisa aktif bareng), JANGAN pakai multiple boolean. Pakai satu state dengan beberapa kemungkinan nilai.
State Machine Thinking
Mesin minuman punya aturan ketat:
- Idle → masukkin uang → Punya Saldo
- Punya Saldo → pilih minuman → Mengeluarkan
- Mengeluarkan → minuman keluar → Idle
Kamu GAK BISA langsung dari Idle ke Mengeluarkan tanpa masukkin uang dulu. Ada ATURAN transisi.
Ini konsep state machine: kumpulan state + aturan perpindahan antar state.
Menerapkan di React
function MesinMinuman() {
const [state, setState] = useState('idle');
const [saldo, setSaldo] = useState(0);
// Definisikan transisi yang VALID
function masukkanUang(jumlah) {
// Hanya bisa dari state 'idle' atau 'punya_saldo'
if (state === 'idle' || state === 'punya_saldo') {
setSaldo(saldo + jumlah);
setState('punya_saldo');
}
// Kalau state-nya 'mengeluarkan', tombol uang diabaikan
}
function pilihMinuman(harga) {
// Hanya bisa dari state 'punya_saldo' DAN saldo cukup
if (state === 'punya_saldo' && saldo >= harga) {
setState('mengeluarkan');
setSaldo(saldo - harga);
// Simulasi minuman keluar
setTimeout(() => {
setState('idle');
}, 2000);
}
}
return (
<div>
<h2>🥤 Mesin Minuman</h2>
<p>Status: {state}</p>
<p>Saldo: Rp {saldo.toLocaleString()}</p>
<button
onClick={() => masukkanUang(2000)}
disabled={state === 'mengeluarkan'}
>
Masukkan Rp 2.000
</button>
<button
onClick={() => pilihMinuman(5000)}
disabled={state !== 'punya_saldo' || saldo < 5000}
>
Teh Botol (Rp 5.000)
</button>
{state === 'mengeluarkan' && <p>⏳ Mengambil minuman...</p>}
</div>
);
}Kenapa State Machine Thinking Penting?
- Mencegah bug: Kamu gak bisa "lompat" ke state yang gak valid
- Mudah di-debug: Kalau ada masalah, cek aja transisi mana yang salah
- Mudah ditambah: Mau tambah state baru? Tinggal definisiin transisinya
- Dokumentasi hidup: Kode-nya sendiri menjelaskan alur aplikasi
Contoh Lengkap: Form Login
Mari kita gabungkan semua konsep di atas dalam satu contoh nyata:
import { useState } from 'react';
function FormLogin() {
// Satu state untuk mengontrol seluruh alur
const [status, setStatus] = useState('idle');
// 'idle' | 'mengetik' | 'mengirim' | 'sukses' | 'error'
// Data form
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [pesanError, setPesanError] = useState('');
// Apakah form bisa di-submit?
const bisaSubmit = email.length > 0 && password.length > 0 && status !== 'mengirim';
// Handler: user ngetik
function handleInputBerubah(field, nilai) {
if (field === 'email') setEmail(nilai);
if (field === 'password') setPassword(nilai);
// Kalau sebelumnya error, reset ke mengetik
if (status === 'error') {
setStatus('mengetik');
setPesanError('');
} else if (status === 'idle') {
setStatus('mengetik');
}
}
// Handler: user submit form
async function handleSubmit(e) {
e.preventDefault();
// Guard: jangan submit kalau lagi ngirim
if (status === 'mengirim') return;
setStatus('mengirim');
try {
// Simulasi API call
await new Promise((resolve, reject) => {
setTimeout(() => {
if (email === 'admin@test.com' && password === '123456') {
resolve();
} else {
reject(new Error('Email atau password salah'));
}
}, 1500);
});
setStatus('sukses');
} catch (err) {
setStatus('error');
setPesanError(err.message);
}
}
// RENDER berdasarkan state
if (status === 'sukses') {
return (
<div className="sukses">
<h2>🎉 Login Berhasil!</h2>
<p>Selamat datang, {email}!</p>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<h2>🔐 Login</h2>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => handleInputBerubah('email', e.target.value)}
disabled={status === 'mengirim'}
placeholder="contoh@email.com"
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => handleInputBerubah('password', e.target.value)}
disabled={status === 'mengirim'}
placeholder="Masukkan password"
/>
</div>
{/* Tampilkan error kalau ada */}
{status === 'error' && (
<p style={{ color: 'red' }}>❌ {pesanError}</p>
)}
<button type="submit" disabled={!bisaSubmit}>
{status === 'mengirim' ? '⏳ Memproses...' : 'Masuk'}
</button>
</form>
);
}Breakdown Alur:
[idle] ──user ngetik──→ [mengetik] ──klik submit──→ [mengirim]
↑ ↓
│ berhasil → [sukses]
│ gagal → [error]
└────user ngetik lagi────────────┘
Ringkasan Cara Berpikir Deklaratif
| Langkah | Apa yang dilakukan | Contoh |
|---|---|---|
| 1 | Identifikasi semua visual state | idle, mengetik, mengirim, sukses, error |
| 2 | Tentukan apa yang memicu perpindahan state | User ngetik, klik submit, API response |
| 3 | Representasikan state dengan useState | const [status, setStatus] = useState('idle') |
| 4 | Hapus state yang gak mungkin terjadi | Gabungin boolean jadi satu enum |
| 5 | Hubungkan event handler ke state setter | onClick → setStatus('mengirim') |
⚠️ Jebakan
Jebakan 1: Terlalu Banyak Boolean
// ❌ JANGAN GINI
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [success, setSuccess] = useState(false);
// Bisa terjadi: loading=true, success=true → MUSTAHIL tapi kode memungkinkan
// ✅ GINI AJA
const [status, setStatus] = useState('idle');
// 'idle' | 'loading' | 'success' | 'error'Jebakan 2: Lupa Disable Input Saat Loading
// ❌ User bisa klik tombol berkali-kali saat loading
<button onClick={handleSubmit}>Kirim</button>
// ✅ Disable saat sedang proses
<button onClick={handleSubmit} disabled={status === 'mengirim'}>
{status === 'mengirim' ? 'Mengirim...' : 'Kirim'}
</button>Jebakan 3: Gak Handle State Error
// ❌ Kalau API gagal, user stuck di loading selamanya
async function handleSubmit() {
setStatus('mengirim');
const hasil = await kirimData(); // Kalau error, gak di-catch!
setStatus('sukses');
}
// ✅ Selalu handle error
async function handleSubmit() {
setStatus('mengirim');
try {
const hasil = await kirimData();
setStatus('sukses');
} catch (err) {
setStatus('error');
setPesanError(err.message);
}
}Jebakan 4: Berpikir Imperatif di React
// ❌ Mindset imperatif: "Saya mau UBAH tampilan"
function handleKlik() {
document.getElementById('pesan').style.display = 'block'; // JANGAN!
}
// ✅ Mindset deklaratif: "Saya mau UBAH state, biar React yang ubah tampilan"
function handleKlik() {
setTampilkanPesan(true); // React otomatis re-render
}Jebakan 5: Impossible State yang Gak Disadari
// ❌ Dua state yang harusnya gak bisa true bersamaan
const [sedangEdit, setSedangEdit] = useState(false);
const [sedangHapus, setSedangHapus] = useState(false);
// Gimana kalau keduanya true? UI jadi bingung
// ✅ Pakai satu state
const [mode, setMode] = useState('tampil'); // 'tampil' | 'edit' | 'hapus'🏋️ Challenge
Challenge 1: Traffic Light (Lampu Lalu Lintas)
Bikin komponen lampu lalu lintas yang otomatis berganti setiap beberapa detik:
- Merah (5 detik) → Kuning (2 detik) → Hijau (5 detik) → Kuning (2 detik) → Merah...
Hint: Pakai useState untuk state warna dan useEffect dengan setTimeout untuk perpindahan otomatis.
Lihat Solusi
import { useState, useEffect } from 'react';
function LampuLaluLintas() {
const [warna, setWarna] = useState('merah');
useEffect(() => {
let durasi;
// Tentukan durasi berdasarkan warna saat ini
if (warna === 'merah') durasi = 5000;
else if (warna === 'kuning') durasi = 2000;
else durasi = 5000; // hijau
const timer = setTimeout(() => {
// Tentukan warna berikutnya
if (warna === 'merah') setWarna('hijau');
else if (warna === 'hijau') setWarna('kuning');
else setWarna('merah'); // dari kuning ke merah
}, durasi);
// Cleanup timer kalau komponen di-unmount
return () => clearTimeout(timer);
}, [warna]); // Jalankan ulang setiap warna berubah
const warnaCSS = {
merah: '#ff0000',
kuning: '#ffcc00',
hijau: '#00cc00',
};
return (
<div style={{ textAlign: 'center', padding: '20px' }}>
<h2>🚦 Lampu Lalu Lintas</h2>
<div
style={{
width: '100px',
height: '100px',
borderRadius: '50%',
backgroundColor: warnaCSS[warna],
margin: '20px auto',
border: '3px solid #333',
}}
/>
<p>Status: {warna.toUpperCase()}</p>
</div>
);
}Challenge 2: Form Multi-Step
Bikin form pendaftaran dengan 3 langkah:
- Isi nama & email
- Isi alamat
- Konfirmasi & kirim
User bisa maju dan mundur antar langkah. Tombol "Kirim" cuma muncul di langkah 3.
Hint: Pakai state langkah (1, 2, atau 3) dan render form yang berbeda berdasarkan langkah.
Lihat Solusi
import { useState } from 'react';
function FormPendaftaran() {
const [langkah, setLangkah] = useState(1);
const [data, setData] = useState({
nama: '',
email: '',
alamat: '',
kota: '',
});
const [status, setStatus] = useState('mengisi'); // 'mengisi' | 'mengirim' | 'sukses'
function updateData(field, nilai) {
setData({ ...data, [field]: nilai });
}
function maju() {
if (langkah < 3) setLangkah(langkah + 1);
}
function mundur() {
if (langkah > 1) setLangkah(langkah - 1);
}
async function handleKirim() {
setStatus('mengirim');
// Simulasi API
await new Promise(resolve => setTimeout(resolve, 1500));
setStatus('sukses');
}
if (status === 'sukses') {
return <h2>🎉 Pendaftaran berhasil! Selamat datang, {data.nama}!</h2>;
}
return (
<div>
<h2>📝 Pendaftaran (Langkah {langkah}/3)</h2>
{/* Progress bar sederhana */}
<div style={{ display: 'flex', gap: '5px', marginBottom: '20px' }}>
{[1, 2, 3].map(l => (
<div
key={l}
style={{
flex: 1,
height: '8px',
backgroundColor: l <= langkah ? '#4CAF50' : '#ddd',
borderRadius: '4px',
}}
/>
))}
</div>
{/* Langkah 1: Data Diri */}
{langkah === 1 && (
<div>
<h3>Data Diri</h3>
<input
placeholder="Nama lengkap"
value={data.nama}
onChange={(e) => updateData('nama', e.target.value)}
/>
<input
placeholder="Email"
type="email"
value={data.email}
onChange={(e) => updateData('email', e.target.value)}
/>
</div>
)}
{/* Langkah 2: Alamat */}
{langkah === 2 && (
<div>
<h3>Alamat</h3>
<input
placeholder="Alamat lengkap"
value={data.alamat}
onChange={(e) => updateData('alamat', e.target.value)}
/>
<input
placeholder="Kota"
value={data.kota}
onChange={(e) => updateData('kota', e.target.value)}
/>
</div>
)}
{/* Langkah 3: Konfirmasi */}
{langkah === 3 && (
<div>
<h3>Konfirmasi Data</h3>
<p><strong>Nama:</strong> {data.nama}</p>
<p><strong>Email:</strong> {data.email}</p>
<p><strong>Alamat:</strong> {data.alamat}</p>
<p><strong>Kota:</strong> {data.kota}</p>
</div>
)}
{/* Tombol navigasi */}
<div style={{ marginTop: '20px' }}>
{langkah > 1 && (
<button onClick={mundur} disabled={status === 'mengirim'}>
← Kembali
</button>
)}
{langkah < 3 && (
<button onClick={maju}>
Lanjut →
</button>
)}
{langkah === 3 && (
<button onClick={handleKirim} disabled={status === 'mengirim'}>
{status === 'mengirim' ? '⏳ Mengirim...' : '✅ Kirim Pendaftaran'}
</button>
)}
</div>
</div>
);
}Challenge 3: Quiz App dengan State Machine
Bikin aplikasi quiz sederhana dengan state: belum_mulai → menjawab → hasil
- Di state
belum_mulai: tampilkan tombol "Mulai Quiz" - Di state
menjawab: tampilkan pertanyaan + pilihan jawaban - Di state
hasil: tampilkan skor
Hint: Simpan juga state untuk soalKe (index soal saat ini) dan skor.
Lihat Solusi
import { useState } from 'react';
const daftarSoal = [
{
pertanyaan: 'Apa ibukota Indonesia?',
pilihan: ['Bandung', 'Jakarta', 'Surabaya', 'Yogyakarta'],
jawabanBenar: 1, // index
},
{
pertanyaan: 'React dibuat oleh perusahaan apa?',
pilihan: ['Google', 'Microsoft', 'Meta (Facebook)', 'Apple'],
jawabanBenar: 2,
},
{
pertanyaan: 'Berapa hasil dari 2 + 2?',
pilihan: ['3', '4', '5', '22'],
jawabanBenar: 1,
},
];
function QuizApp() {
const [fase, setFase] = useState('belum_mulai'); // 'belum_mulai' | 'menjawab' | 'hasil'
const [soalKe, setSoalKe] = useState(0);
const [skor, setSkor] = useState(0);
function mulaiQuiz() {
setFase('menjawab');
setSoalKe(0);
setSkor(0);
}
function jawab(indexPilihan) {
// Cek jawaban
if (indexPilihan === daftarSoal[soalKe].jawabanBenar) {
setSkor(skor + 1);
}
// Pindah ke soal berikutnya atau selesai
if (soalKe + 1 < daftarSoal.length) {
setSoalKe(soalKe + 1);
} else {
setFase('hasil');
}
}
// STATE: Belum mulai
if (fase === 'belum_mulai') {
return (
<div>
<h2>🧠 Quiz Time!</h2>
<p>Ada {daftarSoal.length} soal. Siap?</p>
<button onClick={mulaiQuiz}>Mulai Quiz</button>
</div>
);
}
// STATE: Menjawab
if (fase === 'menjawab') {
const soal = daftarSoal[soalKe];
return (
<div>
<h2>Soal {soalKe + 1}/{daftarSoal.length}</h2>
<p><strong>{soal.pertanyaan}</strong></p>
{soal.pilihan.map((pilihan, index) => (
<button
key={index}
onClick={() => jawab(index)}
style={{ display: 'block', margin: '8px 0', padding: '10px' }}
>
{pilihan}
</button>
))}
</div>
);
}
// STATE: Hasil
return (
<div>
<h2>🏆 Hasil Quiz</h2>
<p>Skor kamu: {skor}/{daftarSoal.length}</p>
<p>
{skor === daftarSoal.length
? '🎉 Sempurna!'
: skor >= daftarSoal.length / 2
? '👍 Lumayan!'
: '📚 Belajar lagi ya!'}
</p>
<button onClick={mulaiQuiz}>Ulangi Quiz</button>
</div>
);
}Kesimpulan
Berpikir deklaratif itu kayak jadi sutradara film. Kamu gak perlu gerakin tangan aktor satu per satu. Kamu cuma bilang: "Di scene ini, kamu sedih. Di scene itu, kamu marah." Aktor (React) yang eksekusi detailnya.
Yang kamu pelajari di bab ini:
- UI deklaratif = kamu deskripsikan "apa yang harus ditampilkan", bukan "bagaimana mengubahnya"
- Identifikasi semua visual state sebelum nulis kode
- Gunakan satu state (enum/string) untuk hal yang saling eksklusif
- Hubungkan event handler ke state transition
- Hindari impossible state dengan menggabungkan boolean yang berkaitan
- State machine thinking membantu bikin alur yang jelas dan bebas bug
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.