useRef — React

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…

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

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>
  )
}

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

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.

Yang akan kamu pelajari