Ref dan DOM

Ref menyediakan cara untuk mengakses simpul DOM atau elemen React yang dibuat dalam render method.

Dalam aliran data React yang umum, props adalah satu-satunya cara bagi komponen induk untuk berinteraksi dengan anaknya. Untuk memodifikasi anak, Anda me-render ulang dengan props yang baru. Tetapi ada beberapa kasus ketika Anda harus memodifikasi anak secara imperatif di luar aliran data yang umum. Anak yang akan dimodifikasi bisa berupa komponen React atau elemen DOM. Pada kedua kasus ini, React menyediakan jalan keluar.

Kapan Harus Menggunakan Ref

Ada beberapa kasus yang cocok untuk ref:

  • Mengelola fokus, pemilihan teks, atau pemutaran media.
  • Memicu animasi secara imperatif.
  • Mengintegrasikan dengan library DOM pihak ketiga.

Hindari penggunaan ref untuk semua yang bisa dilakukan secara deklaratif.

Misalnya, alih-alih mengekspos method open() dan close() pada komponen Dialog, kirimkan props isOpen kepadanya.

Jangan Berlebihan Menggunakan Ref

Godaan pertama mungkin adalah menggunakan ref agar aplikasi “bisa berfungsi”. Jika benar demikian, hentikan sejenak dan pikirkan secara kritis, tempat state harus berada dalam hierarki komponen. Sering kali nantinya ditemukan bahwa tempat yang lebih baik untuk “memiliki” state tersebut adalah di tingkat yang lebih tinggi dalam hierarki. Lihat panduan Memindahkan State ke Atas untuk contohnya.

Catatan

Contoh berikut telah diperbarui untuk menggunakan API React.createRef() yang diperkenalkan dalam React 16.3. Jika Anda menggunakan versi React sebelumnya, kami sarankan untuk menggunakan callback ref.

Membuat Ref

Ref dibuat menggunakan React.createRef() dan disematkan ke elemen React lewat atribut ref. Ref umumnya ditetapkan ke properti instance saat komponen dibuat agar mereka bisa dirujuk dalam komponen.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

Mengakses Ref

Ketika ref dioper ke elemen dalam render, sebuah referensi ke simpul akan tersedia untuk diakses pada atribut current milik ref.

const node = this.myRef.current;

Nilai ref berbeda-beda tergantung jenis simpulnya:

  • Ketika atribut ref digunakan pada elemen HTML, ref yang dibuat dalam konstruktor dengan React.createRef() menerima elemen DOM yang mendasari sebagai properti current miliknya.
  • Saat atribut ref digunakan pada class component khusus, objek ref menerima instans yang dipasang milik komponen sebagai current-nya.
  • Anda tidak diizinkan untuk menggunakan atribut ref pada function component karena komponen tersebut tidak memiliki instans.

Contoh berikut menunjukkan perbedaannya.

Menambahkan Ref pada elemen DOM

Kode berikut menggunakan ref untuk menyimpan referensi ke simpul DOM:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // buat ref untuk menyimpan elemen DOM textInput
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // Fokuskan secara eksplisit pada input teks menggunakan API DOM dasar
    // Catatan: kita sedang mengakses "current" untuk mendapatkan simpul DOM
    this.textInput.current.focus();
  }

  render() {
    // beri tahu React bahwa kita ingin mengasosiasikan *ref* <input>
    // dengan `textInput` yang kita buat dalam konstruktor
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Fokus pada masukan teks"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

React akan menetapkan properti current menjadi elemen DOM saat komponen dipasang dan menetapkannya kembali menjadi null saat dilepas. Pembaruan ref terjadi sebelum lifecycle method componentDidMount atau componentDidUpdate.

Menambahkan Ref ke Class Component

Jika kita ingin membungkus CustomTextInput di atas untuk mensimulasikan peristiwa klik pada masukan teks segera setelah dipasang, kita bisa menggunakan ref untuk mendapatkan akses ke input khususnya dan memanggil method focusTextInput secara manual:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

Perhatikan bahwa ini hanya akan berfungsi jika CustomTextInput dideklarasikan sebagai kelas:

class CustomTextInput extends React.Component {
  // ...
}

Ref dan Function Component

Pada dasarnya, Anda tidak diizinkan untuk menggunakan atribut ref dalam function component karena komponen tersebut tidak memiliki instans:

function MyFunctionComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // Ini *tidak akan* berfungsi!
    return (
      <MyFunctionComponent ref={this.textInput} />
    );
  }
}

Jika Anda ingin mengizinkan seseorang membawa ref ke komponen fungsi Anda, Anda dapat menggunakan forwardRef (kemungkinan bersamaan dengan useImperativeHandle), atau Anda dapat mengubah komponen tersebut menjadi komponen kelas.

Tetapi Anda bisa menggunakan atribut ref di dalam function component selama Anda merujuk ke elemen DOM atau class component:

function CustomTextInput(props) {
  // textInput harus dideklarasikan di sini agar ref bisa merujuknya
  let textInput = React.createRef();

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />
      <input
        type="button"
        value="Fokus pada masukan teks"
        onClick={handleClick}
      />
    </div>
  );
}

Mengekspos Ref DOM ke Komponen Induk

Pada sebagian kecil kasus, Anda mungkin ingin mendapatkan akses ke simpul DOM anak dari komponen induk. Hal ini tidak disarankan karena akan merusak enkapsulasi komponen, tetapi terkadang berguna untuk memicu fokus atau mengukur ukuran atau posisi simpul DOM anak.

Walau Anda bisa menambahkan ref ke child component, ini bukan solusi yang ideal, karena Anda hanya akan mendapatkan instans komponen, bukan simpul DOM. Selain itu, hal ini tidak akan berfungsi dengan function component.

Jika Anda menggunakan React 16.3 atau yang lebih baru, kami sarankan untuk menggunakan ref forwarding untuk kasus semacam ini. Ref forwarding memungkinkan komponen untuk memilih untuk mengekspos semua ref komponen anak sebagai miliknya sendiri. Anda bisa menemukan contoh mendetail tentang cara mengekspos simpul DOM anak ke komponen induk dalam dokumentasi ref forwarding.

Jika Anda menggunakan React 16.2 atau yang lebih lawas, atau jika Anda membutuhkan lebih banyak fleksibilitas dibandingkan dengan yang disediakan oleh ref forwarding, Anda bisa menggunakan pendekatan alternatif ini dan secara eksplisit mengoper sebuah ref sebagai props dengan nama yang berbeda.

Jika dimungkinkan, kami sarankan untuk tidak mengekspos simpul DOM, walau bisa berguna sebagai jalan keluar. Perhatikan bahwa pendekatan ini membutuhkan tambahan kode pada komponen anak Jika Anda sama sekali tidak memiliki kontrol pada implementasi komponen anak, opsi terakhir Anda adalah menggunakan findDOMNode(). Tetapi hal ini tidak disarankan dan akan menjadi deprecated dalam StrictMode.

Callback Ref

React juga mendukung cara lain untuk menetapkan ref yang disebut sebagai “callback ref”, yang memberikan kontrol lebih mendetail kapan ref akan di-set dan di-unset.

Alih-alih mengoper atribut ref yang dibuat oleh createRef(), Anda mengoper sebuah fungsi. Fungsi tersebut menerima instans komponen React atau elemen DOM HTML sebagai argumennya, yang bisa disimpan dan diakses di tempat lain.

Contoh berikut mengimplementasikan sebuah pola umum: menggunakan callback ref untuk menyimpan rujukan ke simpul DOM dalam properti instans.

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // Fokus pada input teks menggunakan API DOM dasar
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // autofocus pada input saat mount
    this.focusTextInput();
  }

  render() {
    // Gunakan callback `ref` untuk menyimpan rujukan ke elemen DOM
    // input teks dalam field instans (misalnya, this.textInput).
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Fokus pada masukan teks"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

React akan memanggil callback ref dengan elemen DOM saat komponen dipasang dan memanggilnya dengan null saat dilepas. Ref dijamin untuk selalu diperbarui sebelum componentDidMount atau componentDidUpdate dijalankan.

Anda bisa mengoper callback ref antarkomponen seperti halnya yang bisa Anda lakukan dengan object ref yang dibuat dengan React.createRef().

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

Pada contoh di atas, Parent mengoper callback ref-nya sebagai props inputRef kepada CustomTextInput dan CustomTextInput mengoper fungsi yang sama sebagai atribut ref khusus kepada <input>. Hasilnya, this.inputElement pada Parent akan disetel ke simpul DOM yang sesuai dengan elemen <input> pada CustomTextInput.

API Legacy: String Ref

Jika sebelumnya Anda bekerja dengan React, Anda mungkin sudah mengenal API lawas dengan atribut ref berupa string, misalnya "textInput", dan simpul DOM yang diakses dengan this.refs.textInput. Kami sarankan untuk tidak melakukannya karena string ref memiliki beberapa masalah dan dipertimbangkan sebagai legacy, serta kemungkinan besar akan dihapus dalam salah satu rilis di masa mendatang.

Catatan

Jika saat ini Anda menggunakan this.refs.textInput untuk mengakses ref, kami sarankan untuk beralih menggunakan callback pattern atau API createRef.

Kekurangan pada callback ref

Jika callback ref didefinisikan sebagai fungsi inline, fungsi ini akan dipanggil dua kali dalam proses pembaruan, yang pertama dengan argumen null, yang kedua dengan argumen elemen DOM. Ini terjadi karena instans baru dari fungsi akan dibuat dalam setiap proses render, karena React harus membersihkan ref yang lama dan menyiapkan yang baru. Anda bisa menghindari hal ini dengan mendefinisikan callback ref sebagau bound method pada kelas, tetapi perhatikan bahwa dalam sebagian besar kasus hal ini tidak berpengaruh.