createPortal(children, domNode) render children vào một DOM node khác — thoát khỏi parent về mặt DOM. Use case: modal, tooltip, dropdown cần thoát overflow: hidden hoặc context z-index của ancestor.
Điểm "ảo diệu": dù DOM tách rời, event vẫn bubble theo React tree, không theo DOM tree.
import { createPortal } from 'react-dom'
function Modal({ children, onClose }) {
return createPortal(
<div className="overlay" onClick={onClose}>
<div className="modal-content">{children}</div>
</div>,
document.getElementById('modal-root') // DOM node khác
)
}
function App() {
return (
<div onClick={() => console.log('App caught click')}>
<Modal>
<button>Click me</button>
</Modal>
</div>
)
}
// → Click button trong modal VẪN log "App caught click"
// dù trong DOM, button nằm trong #modal-root, không nằm trong AppHệ quả:
- ✅ Context, hooks, refs hoạt động bình thường qua portal (cùng React tree).
- ✅ Parent component bắt được event từ portal child — tiện cho modal close-on-overlay-click.
- ⚠️ Nếu cần chặn bubble lên parent, gọi e.stopPropagation() trong portal.
- ⚠️ Đừng nhầm: cấu trúc DOM thay đổi → CSS selector .parent .child không match nữa.
createPortal(children, domNode) renders children into a different DOM node — escaping the parent at the DOM level. Use cases: modals, tooltips, dropdowns that need to escape an ancestor's overflow: hidden or z-index context.
The "magical" part: even though the DOM is separated, events still bubble along the React tree, not the DOM tree.
import { createPortal } from 'react-dom'
function Modal({ children, onClose }) {
return createPortal(
<div className="overlay" onClick={onClose}>
<div className="modal-content">{children}</div>
</div>,
document.getElementById('modal-root') // different DOM node
)
}
function App() {
return (
<div onClick={() => console.log('App caught click')}>
<Modal>
<button>Click me</button>
</Modal>
</div>
)
}
// → Clicking the button inside the modal STILL logs "App caught click"
// even though in the DOM, the button lives in #modal-root, not in AppConsequences:
- ✅ Context, hooks, refs work normally through portals (same React tree).
- ✅ Parent components catch events from portal children — convenient for "close modal on overlay click".
- ⚠️ To stop bubbling to the parent, call e.stopPropagation() inside the portal.
- ⚠️ Watch out: the DOM structure differs → CSS selectors like .parent .child no longer match.