Portal

Portal menyediakan cara utama untuk me-render anak ke dalam simpul DOM yang berada di luar hierarki komponen induk.

ReactDOM.createPortal(child, container)

Argumen pertama (child) berupa anak React yang bisa di-render, misalnya sebuah elemen, string, atau fragment. Argumen kedua (container) merupakan elemen DOM.

Penggunaan

Umumnya saat Anda mengembalikan sebuah elemen dari method render komponen, elemen tersebut dipasang ke DOM sebagai anak pada simpul induk terdekat:

render() {
  // React memasang div baru dan me-render anaknya kepadanya
  return (
    <div>
      {this.props.children}
    </div>
  );
}

Akan tetapi terkadang ada gunanya untuk menyisipkan sebuah anak ke lokasi yang berbeda dalam DOM:

render() {
  // React *tidak* membuat div baru. React me-render anak ke dalam `domNode`.
  // `domNode` berupa simpul DOM apa saja yang valid, tidak tergantung pada 
  // lokasinya dalam DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

Penggunaan umum untuk portal adalah ketika komponen induk memiliki gaya overflow: hidden atau z-index, tetapi Anda harus “memisahkan” anak secara visual dari kontainernya. Misalnya pada dialog, hovercard, atau tooltip.

Catatan:

Saat bekerja dengan portal, perhatikan bahwa mengelola fokus papan ketik menjadi sangat penting.

Untuk dialog modal, pastikan semua pihak bisa berinteraksi dengannya dengan mengikuti WAI-ARIA Modal Authoring Practices.

Coba di CodePen

Event Bubbling lewat Portal

Walau sebuah portal bisa berada di mana saja dalam pohon DOM, portal tersebut berperilaku seperti halnya anak React yang normal. Fitur seperti context bekerja sama persis tanpa tergantung apakah sebuah anak adalah sebuah portal, karena portal masih berada dalam pohon React tanpa memandang posisinya dalam pohon DOM.

Ini mencakup event bubbling. Sebuah event yang dijalankan dari dalam portal akan dipropagasikan ke induknya dalam pohon React yang memuatnya, walau elemen tersebut bukan merupakan induk dalam pohon DOM. Misalnya pada struktur HTML berikut:

<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>

Komponen Parent pada #app-root akan bisa menangkap event bubbling yang belum ditangkap dari simpul sederajat #modal-root.

// Kedua kontainer berikut sederajat dalam DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // Elemen portal disisipkan dalam pohon DOM setelah
    // anak dari Modal dipasang, yang berarti anak tersebut
    // akan dipasang pada simpul DOM yang terpisah. Jika 
    // komponen anak harus disematkan ke dalam pohon DOM
    // segera setelah dipasang, misalnya untuk mengukur dimensi
    // simpul DOM, atau menggunakan 'autoFocus' pada turunannya, 
    // tambahkan state pada Modal dan hanya render para anak saat
    // Modal disisipkan dalam pohon DOM.
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // Ini akan dijalankan ketika tombol pada Child diklik,
    // memperbarui state Parent, walau tombol tersebut
    // bukan turunan langsung dalam DOM.
    this.setState(state => ({
      clicks: state.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Jumlah klik: {this.state.clicks}</p>
        <p>
          Buka DevTools browser
          untuk mengamati bahwa tombol
          bukan anak dari div
          pada handler onClick.
        </p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

function Child() {
  // Event klik pada tombol ini akan meluap ke induk,
  // karena tidak ada atribut 'onClick' yang didefinisikan
  return (
    <div className="modal">
      <button>Klik</button>
    </div>
  );
}

ReactDOM.render(<Parent />, appRoot);

Coba di CodePen

Menangkap event bubbling yang meluap dari portal pada komponen induk mengizinkan pengembangan abstraksi yang lebih fleksibel yang tidak mengandalkan portal. Misalnya jika Anda me-render komponen <Modal />, induk bisa menangkap event-nya tanpa tergantung apakah diimplementasikan dengan portal atau tidak.