useState re-render component tiap state berubah — itu fitur, bukan bug. Tapi kadang kamu butuh simpan nilai yang persist antar render TANPA trigger re-render: pegangan ke DOM element (buat focus input), ID interval (buat clear), nilai yang di-update sering tapi gak perlu di-display. Itu jobs useRef. Plus, ini juga cara satu-satunya akses DOM element langsung dari React — escape hatch ke imperative world saat declarative React gak cukup.
🤔 Bukan pengganti useState.
useState= nilai display di UI (state berubah → re-render).useRef= nilai PRESISTEN tapi UI gak peduli (focus DOM, timer ID). Salah pilih = bug. Mau counter di tampilkan di layar?useState. Mau counter buat tracking jumlah klik tanpa display? BisauseRef. Mau pegang DOM input buat panggil.focus()?useRef.
Akses DOM Element — Use Case Utama
function App() {
const inputRef = useRef(null)
const focusInput = () => {
inputRef.current.focus()
}
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
)
}
ref={inputRef} = pasang ref ke element. React isi .current dengan DOM node setelah render.
Simpan Nilai Tanpa Re-render
function StopWatch() {
const [time, setTime] = useState(0)
const intervalRef = useRef(null) // simpan ID interval
const start = () => {
intervalRef.current = setInterval(() => {
setTime(t => t + 1)
}, 1000)
}
const stop = () => clearInterval(intervalRef.current)
return (
<div>
<p>{time}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
)
}
time di-display → useState. intervalRef.current cuma buat track ID interval (gak displayed) → useRef.
Kunci Properties
useState |
useRef |
|---|---|
| Trigger re-render saat berubah | TIDAK trigger re-render |
| Snapshot per render | Selalu nilai TERKINI |
Akses via destructure [val, setVal] |
Akses via .current |
| Untuk data yang DI-DISPLAY | Untuk data internal / DOM ref |
Akses DOM Setelah Render — Pakai useEffect
function ChatBox({ messages }) {
const bottomRef = useRef(null)
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" })
}, [messages]) // scroll ke bawah tiap message baru
return (
<div className="chat">
{messages.map(m => <Message key={m.id} {...m} />)}
<div ref={bottomRef} />
</div>
)
}
🎭 Analogi sehari-hari
useRef itu kayak kantong kecil rahasia di tas. useState = label tas yang kelihatan dari luar (orang liat tas kuning, pas kamu ganti tas merah, semua liat). useRef = isi kantong rahasia di dalam tas — bisa berubah berkali-kali, tapi orang luar gak ngeh sampai kamu sengaja ngeluarinnya. Pas kamu butuh nyimpen sesuatu yang fungsional tapi gak perlu "diumumkan ulang" tiap kali berubah (KTP, kunci, ID interval), useRef tempatnya. Plus, useRef juga kayak tag GPS yang nempel di paket — kamu bisa "tunjuk" ke object yang udah ada (DOM element) tanpa nyimpen object-nya sendiri.
⚠️ Jebakan yang sering ditemui
- Akses
.currentsaat render —<p>{ref.current?.value}</p>= stale, mungkin null. DOM ref di-set SETELAH render, akses di useEffect/event handler. - Mutate ref untuk trigger re-render = GAK JALAN.
ref.current = 5gak bikin re-render. Pakai useState kalau perlu display. ref.current.focus()di mount — element belum mounted di body function. Pakai useEffect atau callback ref.- Pass ref ke component custom —
<MyComponent ref={ref}>butuhforwardRefdi MyComponent. Forward ke DOM atau imperative handle. - Lupa initial value —
useRef()(no arg) →.current = undefined. Biasanya pakaiuseRef(null). - Pakai useRef sebagai cache hasil komputasi — bisa, tapi
useMemolebih semantik untuk itu. setIntervalID di state, bukan ref — re-render karena ID berubah = bug aneh. Selalu pakai useRef untuk ID timer/listener.
🎯
useRefvsuseState— pilih mana?
- Nilai DI-DISPLAY di UI →
useState(counter, input value, list items)- Pegangan ke DOM element →
useRef(focus, scrollIntoView, measure)- Timer ID, interval ID, subscription ID →
useRef(gak displayed, gak butuh re-render)- Track previous value (compare prev vs current state) →
useRefdi useEffect- Cache hasil komputasi →
useMemo(lebih semantik) atauuseRef- Mutable instance seperti class instance →
useRefAturan praktis: butuh re-render saat berubah? useState. Cuma butuh persist + mutate? useRef.
TL;DR: useRef(initial) = nilai persist antar render TANPA trigger re-render. Akses via .current. Use case utama: (1) pegangan ke DOM element (<input ref={ref}> lalu ref.current.focus()), (2) simpan timer ID/listener ID, (3) track previous value. Beda dari useState: gak re-render saat ubah .current. DOM ref di-set SETELAH render — akses di useEffect/event handler, BUKAN di body component.