Back to blog

Feb 09, 2025

Giới thiệu React Portal: Khi bạn cần cánh cửa thần kỳ cho Modal

PH

Phineas

@Phineas

cover

"Hãy tưởng tượng bạn đang làm việc trên một trang web và muốn chạy một chiến dịch khuyến mãi thật ngầu, kiểu như: "Mua 1 ly cà phê, tặng 2 cái nhìn đầy thiện cảm!". Và dĩ nhiên, cách tuyệt vời để thông báo chiến dịch đó là tạo một modal bật lên giữa màn hình khi khách vừa vào trang. Nghe thì dễ đúng không? Bạn bắt đầu viết modal, nó hiện lên, nhưng ôi không—các kiểu overflow: hidden và z-index bỗng nhiên kéo modal tội nghiệp đó chui vào một góc xó xỉnh nào đó trên trang. Bạn cố gắng xử lý bằng nhiều cách, sử dụng position: fixed... balabla, nhưng vẫn không triệt để, vì cái navbar của bạn cũng dùng position: fixed và có z-index còn cao hơn cái modal của bạn. Giờ đây, bạn thấy modal của mình lấp ló sau navbar, như một ly cà phê nguội lạnh giữa cơn mưa. Bạn đã thử tăng z-index của modal lên cao ngất ngưởng, nhưng mãi mà nó vẫn không thể "trốn thoát" khỏi ảnh hưởng của navbar."

Ví dụ trên mình đưa ra là câu chuyện thực tế mà mình từng mắc phải. Thì lúc này là lúc React Portal xuất hiện như một "lối thoát hiểm" tuyệt vời cho mình.

1. React Portal là gì?

Cứ tưởng tượng Portal như một "cánh cửa thần kỳ" để đưa Modal của bạn chạy ra khỏi đống rối ren của các thành phần khác. Với Portal, bạn có thể "triệu hồi" một component từ trong cây DOM của mình và ném nó ra ngoài DOM của component cha để hiển thị trực tiếp trên giao diện, nhưng vẫn giữ được các sự kiện và hành vi như thể nó vẫn nằm trong cây DOM đó. Nói cách khác, Portal cho phép bạn tách Modal của mình ra khỏi các thành phần xung quanh để không bị ràng buộc bởi bố cục, class CSS của cha nó.

2. Tại sao Portal lại cực kỳ tiện dụng cho các modal?

React Portal được sinh ra để dành cho những chiến dịch "tạm bợ" nhưng phải thật "bắt mắt", như popup của đợt giảm giá chẳng hạn. Cái Portal giúp tránh các vấn đề CSS như:

  • Không bị "cắt đầu cắt đuôi" bởi overflow: Đảm bảo modal hiện trọn vẹn trên màn hình thay vì bị cắt xén.
  • Thoát khỏi những class CSS của phần tử cha: Các thuộc tính "cứng đầu" như position: relative của các thành phần cha không còn làm khó modal.
  • Dễ dàng quản lý các thông báo quan trọng: Portal giúp bạn "bắn" modal vào vị trí dễ thấy nhất mà không cần chỉnh sửa quá nhiều CSS phức tạp.

3. Cách sử dụng React Portal

Sau 2 phần giải thích phía trên, hy vọng các bạn có thể hiểu nôm na nó là gì rồi.

Thì để sử dụng React Portal, bạn chỉ cần sử dụng ReactDOM.createPortal, nhận hai tham số: nội dung cần render và phần tử DOM mà bạn muốn render nội dung vào.

Ví dụ:

Bước 1: Tạo một Portal common để dễ sử dụng

javascript
import { ReactNode, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

type Props = {
  id?: string;
  children?: ReactNode;
};

/** Portal component */
export const Portal: React.FC<Props> = ({ id, children }) => {
  if (!id) {
    return null;
  }

  const container = document.getElementById(id);

  if (!container) {
    console.error(`No container found with id: ${id}`);
    return null;
  }

  return ReactDOM.createPortal(children, container);
};

Bước 2: Tạo Modal sử dụng Portal

javascript
import React from 'react';
import { Portal } from './Portal'; // Import Portal từ file đã tạo

type ModalProps = {
  isOpen: boolean;
  onClose: () => void;
};

const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children }) => {
  if (!isOpen) return null;

  return (
    <Portal id="modal-root">
      <div className="modal-overlay">
        <div className="modal-content">
          <button onClick={onClose}>Đóng</button>
          {children}
        </div>
      </div>
    </Portal>
  );
};

export default Modal;

Bước 3: Sử dụng Modal trong ứng dụng

javascript
import React, { useState } from 'react';
import Modal from './Modal';

const App: React.FC = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  return (
    <div>
      <h1>Chương trình Khuyến mãi!</h1>
      <button onClick={() => setIsModalOpen(true)}>Mở Modal</button>
      <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
        <h2>Mua 1 ly cà phê, tặng 2 cái nhìn đầy thiện cảm!</h2>
      </Modal>
    </div>
  );
};

export default App;

Bước 4: Thêm phần tử HTML cho Portal

Đảm bảo bạn có phần tử mục tiêu trong file index.html của bạn:

javascript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React Portal Example</title>
</head>
<body>
    <div id="root"></div>
    <div id="modal-root"></div> <!-- Phần tử nơi Portal sẽ render,  -->
</body>
</html>

Bình thường, các cấu trúc DOM sẽ được render trong id là "root", nhưng vì mình đặt modal nằm ở ngoài root nên sẽ không bị ảnh hưởng bởi những thằng khác.

Ở đây mình thêm vào index.html nhưng các bạn muốn thêm ở 1 component nào khác cũng được, tùy tình huống, chỉ cần khai báo đúng id trùng với id của portal muốn đẩy ra là được.

Kết luận

React Portal không chỉ là một kỹ thuật tiện dụng để xử lý modal, mà còn là “vị cứu tinh” cho các chiến dịch mà bạn muốn làm bật lên trên màn hình. Hãy thử dùng Portal cho chiến dịch giảm giá tiếp theo và trải nghiệm một sự khác biệt trong việc quản lý giao diện một cách linh hoạt và hiệu quả.

Nếu các bạn muốn tìm hiểu bên Vue, chúng ta cũng có 1 kỹ thuật tương tự teleport. Cảm ơn đã vì đã đọc đến đây, chúc một ngày tốt lành!