useRef memberi komponen sebuah kotak yang diingat React di antara render, dan isinya boleh kamu ganti kapan saja tanpa memicu render ulang. Kotak ini dipakai untuk dua hal: menyimpan nilai yang gak perlu tampil, dan memegang elemen DOM asli, misalnya untuk memindahkan kursor ke sebuah input. Ingat papan skor futsal di pelajaran useState? Skor di papan itu state: begitu diganti, semua penonton langsung melihat angka baru. Ref itu buku saku wasit: dia bisa menulis kapan saja, gak ada yang melihat catatannya berubah, tapi catatannya tetap ada waktu dibuka lagi.
🧪 Tebakan cepat: sebuah tombol menjalankan
clicksRef.current++, dan komponennya menampilkan{clicksRef.current}. Tombol itu diklik tiga kali. Angka di layar jadi 3 atau tetap 0? Tebak dulu, jawabannya ada di bagian "Ref berubah tanpa render ulang".
Kotak yang diingat antar render
const clicksRef = useRef(0)
console.log(clicksRef) // { current: 0 }
clicksRef.current = 5 // ganti isinya
console.log(clicksRef.current) // 5
useRef(0)mengembalikan object dengan satu properti,current, yang awalnya berisi0.- Di tiap render, React mengembalikan object yang sama, jadi isi
currentgak hilang waktu komponen render ulang. Beda denganletbiasa, yang dibuat ulang dari awal tiap render. - Kamu membaca dan mengganti isinya lewat
.current:clicksRef.current = 5,clicksRef.current++. - Mengganti
.currentgak memberi tahu React apa-apa, jadi gak ada render ulang. Itu bedanya dengan setteruseState. - Di project asli, hook diimpor dulu:
import { useRef } from 'react'. Di editor latihan,useRef,useState, danuseEffectsudah tersedia. Baris import boleh ditulis, tapi gak wajib.
Ref berubah tanpa render ulang
function App() {
const clicksRef = useRef(0)
const [clicks, setClicks] = useState(0)
function addRef() {
clicksRef.current++ // ganti isi ref: gak ada render ulang
console.log(clicksRef.current)
}
return (
<div>
<p>ref: {clicksRef.current} · state: {clicks}</p>
<button onClick={addRef}>clicksRef.current++</button>
<button onClick={() => setClicks((c) => c + 1)}>setClicks</button>
</div>
)
}
| Kejadian | clicksRef.current |
Console | Di layar |
|---|---|---|---|
| Render pertama | 0 | ref: 0 · state: 0 | |
Klik clicksRef.current++ |
1 | 1 | ref: 0 · state: 0 |
| Klik lagi | 2 | 2 | ref: 0 · state: 0 |
| Klik lagi | 3 | 3 | ref: 0 · state: 0 |
Klik setClicks |
3 | ref: 3 · state: 1 |
Console mencetak 1, 2, 3, karena .current langsung berubah dan selalu berisi nilai terbaru. Tapi layar tetap 0, karena gak ada render ulang. Itu jawaban tebakannya. Begitu state berubah dan komponen render ulang, barulah angka 3 di dalam ref terbaca dan tampil.
Menghitung render
function App() {
const [value, setValue] = useState(0)
const renderCount = useRef(0)
useEffect(() => {
renderCount.current = renderCount.current + 1 // naik sesudah tiap render
}) // tanpa dependency array: jalan sesudah setiap render
return (
<div>
<span id="render-count">{renderCount.current}</span>
<button id="update" onClick={() => setValue((v) => v + 1)}>Update</button>
</div>
)
}
| Kejadian | Tampil di #render-count |
renderCount.current sesudah effect |
|---|---|---|
| Render pertama | 0 | 1 |
| Klik Update, render ke-2 | 1 | 2 |
| Klik Update, render ke-3 | 2 | 3 |
Effect jalan sesudah render, jadi angka yang tampil adalah jumlah render sebelum render ini: awalnya 0, sesudah satu klik jadi 1. Mengubah ref di effect gak memicu render lagi. Kalau yang dipakai state, setRenderCount di effect tanpa dependency array akan memicu render, lalu effect lagi, terus tanpa henti. Biasanya React menyarankan jangan membaca .current waktu render. Di sini sengaja, supaya kelihatan bahwa ref gak ikut memicu render.
Memegang elemen DOM: fokus ke input
function App() {
const inputRef = useRef(null)
return (
<div>
<input id="nama" ref={inputRef} />
<button id="focus" onClick={() => inputRef.current.focus()}>Focus</button>
</div>
)
}
useRef(null): waktu ref dibuat, elemennya belum ada, jadi mulai darinull.ref={inputRef}adalah atribut khusus React. Sesudah render, React mengisiinputRef.currentdengan elemen DOM<input>yang asli, object yang sama dengan hasildocument.getElementById('nama').inputRef.current.focus()memanggil method bawaan elemen input, yang memindahkan kursor ketik ke sana. Gak ada state yang berubah, jadi gak ada render ulang.- Panggil di event handler atau effect, jangan di badan komponen. Waktu render pertama berjalan, elemennya belum ada, dan
inputRef.currentmasihnull.
State atau ref?
Pakai useState |
Pakai useRef |
|---|---|
| Nilainya tampil di layar | Nilainya cuma dipakai di balik layar |
| Mengubahnya memicu render ulang | Mengubahnya gak memicu render |
| Contoh: jumlah barang di keranjang, teks yang diketik | Contoh: id dari setInterval, elemen DOM, hitungan render |
⚠️ Jebakan yang sering ketemu
- Menampilkan data dari ref dan berharap layar ikut berubah. Layar cuma diperbarui waktu render ulang. Kalau nilainya harus tampil, simpan di state.
inputRef.focus()tanpa.current.inputRefcuma pembungkusnya. Elemennya ada diinputRef.current.- Menaikkan hitungan render di badan komponen.
renderCount.current++di luar effect jalan sebelum angkanya ditampilkan, jadi angkanya mulai dari 1, bukan 0. Naikkan diuseEffecttanpa dependency array. - Lupa
ref={inputRef}di elemennya. Tanpa atribut itu,inputRef.currenttetapnull, dan.focus()error.
TL;DR: useRef(awal) membuat kotak { current: awal } yang sama di setiap render. Baca dan ganti isinya lewat .current. Mengganti .current gak memicu render ulang, jadi console.log(countRef.current) sesudah countRef.current++ tiga kali mencetak 1, 2, 3, sementara layar tetap menampilkan nilai lama sampai ada render ulang. Untuk elemen DOM, buat useRef(null), pasang ref={inputRef}, lalu panggil inputRef.current.focus() di event handler. Hitungan render naik di useEffect tanpa dependency array, jadi yang tampil awalnya 0 dan jadi 1 sesudah satu render ulang. Nilai yang harus tampil di layar disimpan di state, sisanya boleh di ref.