Mereferensikan Nilai menggunakan Refs

Ketika Anda ingin sebuah komponen untuk “mengingat” beberapa informasi, tetapi Anda tidak mau informasi tersebut memicu render baru, Anda dapat menggunakan ref.

Anda akan mempelajari

  • Bagaimana cara menambahkan ref pada komponen Anda
  • Bagaimana cara memperbarui nilai ref
  • Perbedaan antara refs dan state
  • Bagaimana cara menggunakan refs dengan aman

Menambahkan sebuah ref pada komponen Anda

Anda dapat menambahkan sebuah ref ke komponen dengan mengimpor useRef Hook dari React:

import { useRef } from 'react';

Di dalam komponen Anda, panggil useRef Hook dan berikan nilai awal yang Anda ingin referensikan sebagai satu-satunya argumen. Misalnya, berikut adalah ref yang mempunyai nilai 0:

const ref = useRef(0);

useRef mengembalikan sebuah objek seperti ini:

{
current: 0 // Nilai yang Anda berikan ke useRef
}
Sebuah panah dengan tulisan 'current' dimasukkan ke dalam saku dengan tulisan 'ref'.

Ilustrasi oleh Rachel Lee Nabors

Anda dapat mengakses nilai saat ini dari ref tersebut melalui properti ref.current. Nilai ini bersifat mutable (dapat diubah), yang artinya Anda dapat membaca dan mengubah nilainya. Ini seperti kantong rahasia dari komponen Anda yang tidak dilacak oleh React. (Inilah yang membuatnya menjadi “jalan keluar” dari arus data satu arah pada React - lebih lanjut tentang hal ini akan dijelaskan di bawah!)

Di sini, tombol akan menambahkan nilai dari ref.current setiap kali diklik:

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('Anda mengeklik ' + ref.current + ' kali!');
  }

  return (
    <button onClick={handleClick}>
      Klik saya!
    </button>
  );
}

Ref tersebut mengacu pada sebuah angka, tetapi, seperti state, Anda juga dapat mengacu pada tipe data apa pun: sebuah string, objek, atau bahkan sebuah fungsi. Berbeda dengan state, ref adalah sebuah objek JavaScript biasa dengan properti current yang dapat Anda baca dan ubah nilainya.

Perhatikan bahwa komponen tidak di-render ulang setiap kali nilai pada ref ditambahkan. Seperti state, nilai dari refs akan tetap disimpan atau dipertahankan oleh React saat render ulang terjadi. Namun mengubah state akan memicu render ulang pada komponen, sementara ref tidak akan melakukannya!

Contoh: Membangun stopwatch

Anda dapat menggabungkan refs dan state dalam satu komponen. Sebagai contoh, mari buat stopwatch yang dapat dijalankan atau dihentikan oleh pengguna dengan menekan sebuah tombol. Untuk menampilkan berapa waktu yang telah berlalu sejak pengguna menekan tombol “Mulai”, Anda perlu melacak kapan tombol “Mulai” ditekan dan waktu saat ini. Informasi ini digunakan untuk rendering, sehingga Anda akan menyimpannya dalam state:

const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);

Ketika pengguna menekan “Mulai”, Anda akan menggunakan setInterval untuk memperbarui waktu setiap 10 milidetik:

import { useState } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);

  function handleStart() {
    // Mulai menghitung.
    setStartTime(Date.now());
    setNow(Date.now());

    setInterval(() => {
      // Memperbarui waktu saat ini setiap 10 milidetik.
      setNow(Date.now());
    }, 10);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Waktu yang telah berlalu: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Mulai
      </button>
    </>
  );
}

Ketika tombol “Berhenti” ditekan, Anda perlu membatalkan interval yang ada sehingga komponen tersebut berhenti memperbarui nilai dari state now. Anda dapat melakukannya dengan memanggil clearInterval, tetapi Anda harus memberikan interval ID yang sebelumnya dikembalikan oleh pemanggilan setInterval saat pengguna menekan “Mulai”. Anda perlu menyimpan interval ID tersebut di suatu tempat. Karena interval ID tidak digunakan untuk rendering, Anda dapat menyimpannya dalam ref:

import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Waktu yang telah berlalu: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Mulai
      </button>
      <button onClick={handleStop}>
        Berhenti
      </button>
    </>
  );
}

Ketika sebuah informasi digunakan untuk rendering, simpanlah di dalam state. Ketika sebuah informasi hanya dibutuhkan oleh event handler dan perubahan informasi tersebut tidak memerlukan render ulang, menggunakan ref mungkin akan lebih efisien.

Perbedaan antara refs dan state

Mungkin Anda berpikir bahwa ref terlihat kurang “ketat” dibandingkan dengan state-Anda dapat memutasi ref daripada selalu harus menggunakan fungsi pengaturan state, misalnya. Tetapi dalam kebanyakan kasus, Anda akan ingin menggunakan state. Ref adalah “jalan keluar” yang tidak sering Anda butuhkan. Berikut adalah perbandingan antara state dan ref:

refstate
useRef(initialValue) mengembalikan { current: initialValue }useState(initialValue) mengembalikan nilai saat ini dari sebuah state dan sebuah fungsi pengatur state. ( [value, setValue])
Tidak memicu render ulang ketika Anda mengubahnya.Memicu render ulang ketika Anda mengubahnya.
Mutable—Anda dapat memodifikasi dan memperbarui nilai current di luar proses rendering.Immutable—Anda harus menggunakan fungsi pengatur state untuk memodifikasi state dan memicu render ulang.
Anda sebaiknya tidak membaca (atau menulis) nilai current selama proses rendering.Anda dapat membaca state kapan saja. Namun, setiap render terjadi state memiliki snapshot sendiri yang tidak berubah.

Berikut adalah tombol penghitung yang diimplementasikan dengan state:

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      Anda mengeklik {count} kali
    </button>
  );
}

Karena nilai count ditampilkan, maka masuk akal untuk menggunakan nilai state untuk hal tersebut. Ketika nilai hitung diatur dengan setCount(), React akan me-render ulang komponen dan layar akan diperbarui untuk mencerminkan hitungan baru.

Jika Anda mencoba mengimplementasikan ini dengan menggunakan ref, React tidak akan pernah melakukan render ulang pada komponen, sehingga Anda tidak akan pernah melihat perubahan hitungan! Lihat bagaimana mengeklik tombol ini tidak memperbarui teks-nya:

import { useRef } from 'react';

export default function Counter() {
  let countRef = useRef(0);

  function handleClick() {
    // Hal ini tidak memicu render ulang pada komponen
    countRef.current = countRef.current + 1;
  }

  return (
    <button onClick={handleClick}>
      Anda mengeklik {countRef.current} kali
    </button>
  );
}

Inilah mengapa membaca ref.current selama proses render akan menghasilkan kode yang tidak dapat diandalkan. Jika kamu membutuhkan akses nilai pada saat proses render, lebih baik gunakan state.

Pendalaman

Bagaimana cara useRef bekerja di dalamnya?

Meskipun kedua useState dan useRef disediakan oleh React, pada prinsipnya useRef dapat diimplementasikan di atas useState. Anda dapat membayangkan bahwa di dalam React, useRef diimplementasikan seperti ini:

// Di dalam React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}

Pada saat render pertama kali, useRef akan mengembalikan nilai { current: initialValue }. Objek ini akan disimpan oleh React, sehingga pada saat render berikutnya, objek yang sama akan dikembalikan. Perhatikan bahwa dalam contoh ini, pengatur dari state tidak digunakan. Pengatur state tidak diperlukan karena useRef selalu harus mengembalikan objek yang sama!

React menyediakan versi bawaan dari useRef karena cukup umum dalam praktiknya. Namun, Anda bisa memikirkannya sebagai state biasa tanpa pengatur. Jika Anda akrab dengan pemrograman berorientasi objek, ref mungkin mengingatkan Anda pada instance fields—tetapi bukannya this.something, Anda menulis somethingRef.current.

Kapan untuk menggunakan refs

Biasanya, Anda akan menggunakan ref ketika komponen Anda perlu “keluar” dari React dan berkomunikasi dengan API eksternal - seringkali API peramban yang tidak mempengaruhi tampilan komponen. Berikut adalah beberapa situasi langka di mana Anda bisa menggunakan refs:

Jika komponen Anda perlu menyimpan beberapa nilai, tetapi tidak mempengaruhi logika rendering, pilihlah penggunaan refs.

Praktik terbaik menggunakan refs

Mengikuti prinsip-prinsip ini akan membuat komponen Anda lebih dapat diprediksi:

  • Gunakan refs sebagai “jalan keluar”. Refs berguna ketika Anda bekerja dengan sistem eksternal atau API peramban. Jika sebagian besar logika aplikasi dan aliran data bergantung pada refs, mungkin perlu untuk mempertimbangkan kembali pendekatan yang digunakan.

  • Jangan membaca atau menulis ref.current selama proses rendering. Jika beberapa informasi dibutuhkan selama rendering, gunakan state sebagai gantinya. Karena React tidak mengetahui perubahan pada ref.current, bahkan membacanya selama rendering membuat perilaku komponen sulit diprediksi. (Satu-satunya pengecualian untuk ini adalah kode seperti if (!ref.current) ref.current = new Thing() yang hanya mengatur ref sekali selama render pertama.)

Keterbatasan dari state di React tidak berlaku pada refs. Sebagai contoh, state bertindak seperti snapshot untuk setiap render dan tidak memperbarui sinkron secara langsung. Namun ketika Anda memutasi nilai saat ini dari sebuah ref, perubahannya langsung terjadi:

ref.current = 5;
console.log(ref.current); // 5

Ini karena ref itu sendiri adalah objek JavaScript biasa, dan karena itu berperilaku seperti objek biasa.

Anda juga tidak perlu khawatir tentang menghindari mutasi saat bekerja dengan ref. Selama objek yang Anda ubah tidak digunakan untuk rendering, React tidak peduli apa yang Anda lakukan dengan ref atau isinya.

Refs dan DOM

Anda dapat memberikan nilai apa pun kepada ref. Namun, penggunaan ref yang paling umum adalah untuk mengakses sebuah elemen DOM. Misalnya, hal ini berguna jika Anda ingin memberi fokus pada sebuah input secara programatik. Ketika Anda mengoper sebuah ref ke dalam atribut ref di JSX, seperti <div ref={myRef}>, React akan menempatkan elemen DOM yang sesuai ke dalam myRef.current. Ketika elemen tersebut dihapus dari DOM, React akan mengubah myRef.current menjadi null. Anda dapat membaca lebih lanjut tentang hal ini di Memanipulasi DOM dengan Refs.

Rekap

  • Ref adalah “jalan keluar” untuk menyimpan nilai yang tidak digunakan untuk me-render. Anda tidak akan membutuhkannya terlalu sering.
  • Ref adalah objek JavaScript biasa dengan satu properti yang disebut current, yang dapat Anda baca atau mengaturnya.
  • Anda dapat meminta React untuk memberikan Anda sebuah ref dengan memanggil Hook useRef.
  • Seperti state, ref memungkinkan Anda mempertahankan informasi antara render ulang dari komponen.
  • Tidak seperti state, mengatur nilai current dari ref tidak memicu render ulang.
  • Jangan membaca atau mengubah ref.current selama me-render. Hal ini membuat perilaku komponen Anda sulit diprediksi.

Tantangan 1 dari 4:
Memperbaiki chat input yang rusak

Ketikkan pesan dan klik “Kirim”. Anda akan melihat ada penundaan tiga detik sebelum Anda melihat alert “Terkirim!“. Selama penundaan ini, Anda dapat melihat tombol “Undo”. Klik tombol “Undo”. Tombol “Undo” ini seharusnya menghentikan pesan “Terkirim!” untuk muncul. Ini dilakukan dengan memanggil clearTimeout untuk ID waktu tunggu yang disimpan selama handleSend. Namun, bahkan setelah “Undo” diklik, pesan “Terkirim!” tetap muncul. Temukan mengapa ini tidak berfungsi dan perbaikilah.

import { useState } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  let timeoutID = null;

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('Terkirim!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? 'Mengirim...' : 'Kirim'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          Undo
        </button>
      }
    </>
  );
}