Bayangin tombol Like di Instagram. Di-klik — angka likenya naik 1, ikon hati berubah merah, animasi heartbeat jalan. Klik lagi — semua kebalik. Komponen yang sama, tapi tampilannya berubah-ubah seiring waktu. Itu state: data yang bisa berubah di dalam komponen, dan tiap kali berubah → React otomatis re-render UI biar sinkron sama data. Tanpa state, komponen kamu cuma "foto statis" yang gak bisa respon ke aksi user. Dengan state, komponen jadi "hidup" — interaktif, reaktif, dinamis.
💡
useState= hook paling fundamental. Hampir tiap komponen interaktif di React mulai dariuseState. Form input, tombol toggle, modal, counter, theme switcher — semua dasarnyauseState. Kuasai hook ini = unlock 80% kemampuan React. Hook lain (useEffect, useReducer, dll) dibangun di atas konsep yang sama.
import { useState } from "react"
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Hitungan: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
Cara Kerja useState
useState(initialValue) return ARRAY dengan 2 elemen — destructure jadi pair:
count— nilai state saat ini (read-only, JANGAN ubah langsung)setCount— fungsi setter untuk update state- Saat
setCountdipanggil → React re-render komponen dengan nilai baru → UI auto-update
const [count, setCount] = useState(0)
// ^value ^setter ^initial
Updater Function — Lebih Aman
// CARA BIASA — bisa stale kalau dipanggil cepat berturut-turut
setCount(count + 1)
// UPDATER FUNCTION — pakai ini kalau update berdasarkan state sebelumnya
setCount(prev => prev + 1)
Bedanya keliatan pas setCount dipanggil 3x dalam 1 event handler:
const handleClick = () => {
setCount(count + 1) // count = 0, set ke 1
setCount(count + 1) // count masih 0 (belum re-render!), set ke 1 (sama!)
setCount(count + 1) // sama. Hasil: count = 1, BUKAN 3
}
// Dengan updater:
const handleClick = () => {
setCount(prev => prev + 1) // 0 → 1
setCount(prev => prev + 1) // 1 → 2
setCount(prev => prev + 1) // 2 → 3 ✓
}
State untuk Object/Array — Wajib Buat Baru
// SALAH — mutate langsung, React gak detect change
const [user, setUser] = useState({ name: "Budi", age: 17 })
user.age = 18 // ❌ mutate
setUser(user) // React lihat object SAMA, gak re-render
// BENAR — buat object baru dengan spread
setUser({ ...user, age: 18 }) // ✓ object baru, React re-render
// Array juga sama:
setItems([...items, newItem]) // tambah
setItems(items.filter(x => x !== y)) // hapus
setItems(items.map(x => x.id === id ? {...x, done: true} : x)) // update
🎭 Analogi sehari-hari
useState itu kayak papan tulis kelas. Pas guru mau ganti tulisan, dia gak nyamberin papan langsung pake spidol baru — dia hapus dulu yang lama, terus tulis yang baru (setCount(prev => prev + 1)). Papan tulis sama, tulisan beda. Tiap kali tulisan ganti, semua murid yang lagi liat papan otomatis ngeliat yang baru — itu re-render. Kalau guru cuma bikin coretan kecil di tulisan lama (mutate object), murid-murid yang udah nyatet "papan udah keisi nih" gak akan ngeh ada yang berubah. Makanya: SELALU bikin "tulisan baru" (object/array baru), JANGAN coret-coret yang lama.
⚠️ Jebakan yang sering ditemui
- Mutate state langsung —
count++,arr.push(x),obj.x = y. React pakai shallow comparison untuk detect change. Reference yang sama = "gak berubah" → gak re-render. SELALU bikin baru. - Stale closure di event handler —
setCount(count + 1)3x berturut-turut bukan hasil 3, cuma 1 (semua bacacountlama). Pakai updatersetCount(prev => prev + 1). - State derived dari props/state lain —
const [fullName, setFullName] = useState(firstName + lastName)= anti-pattern, gak update pas firstName/lastName ubah. Hitung langsung:const fullName = firstName + " " + lastName. useStatedi dalam if/loop/condition — VIOLATES Rules of Hooks. Hook harus dipanggil di TOP-LEVEL component, urutan konsisten tiap render.- Set state pas render —
setCount(0)langsung di body fungsi (bukan di event/effect) = infinite loop. Hanya di event handler atau useEffect. - Ngira state update sync —
setCount(5); console.log(count)masih log nilai LAMA (state baru jadi setelah re-render). Kalau butuh nilai baru, pakai useEffect dengan deps[count].
🎯 Pilih bentuk state — gimana caranya?
- Nilai independen sederhana (counter, toggle) →
useState(0),useState(false)- String input →
useState("")- Object terkait (form fields, settings) →
useState({ name: "", email: "" })lalusetForm({ ...form, name })- List items →
useState([])lalu spread/filter/map untuk update- State KOMPLEKS dengan banyak action (cart dengan add/remove/update/clear) →
useReducer(lesson nanti)- State yang BERSAMA banyak komponen → lifting state up atau Context API
Aturan praktis: mulai dengan
useStatesimpel. Naik keuseReducerkalau handler-nya udah membesar (5+ jenis update).
🧪 Tebakan cepat — output apa? Tanpa run, tebak hasil setelah klik tombol:
const [count, setCount] = useState(0) const handleClick = () => { setCount(count + 1) setCount(count + 1) setCount(count + 1) } // Klik tombol sekali — count jadi berapa?Jawaban: 1, bukan 3. Karena
countmasih 0 saat 3 panggilan, semua kompute0 + 1. Fix: pakai updatersetCount(prev => prev + 1)→ hasil 3. Bug paling klasik di useState.
TL;DR: useState(initial) return [value, setter]. Setter dipanggil → komponen re-render dengan nilai baru. WAJIB bikin object/array baru untuk update (jangan mutate — setUser({...user, age: 18})). Pakai updater setX(prev => prev + 1) kalau update berdasarkan state lama atau panggil setter berturut-turut. State update gak sync — setCount(5); console.log(count) masih lama. Hook hanya di top-level component (bukan di if/loop). State kompleks → useReducer.