Portals
The last topic in the Advanced React category -- a way to render a component to a different DOM node than its position in the React tree: Portals.
What Is a Portal? Getting Started with createPortal
react-dom's createPortal function lets us render a component to a
different place in the DOM:
import { createPortal } from "react-dom";
function BasicPortalExample() {
// createPortal(child, container), `child`'ı normal React ağacındaki
// YERİNE değil, DOM'daki farklı bir düğüme (`container`) render eder --
// burada `document.body`'nin kendisine. Component ağacında (React
// DevTools'ta) hâlâ BasicPortalExample'ın İÇİNDE görünür, ama gerçek
// DOM'da tamamen farklı bir yerdedir.
return createPortal(<p className="tooltip">I'm rendered directly on body!</p>, document.body);
}
createPortal(child, container) renders child into the container
DOM node, instead of its normal position in the React tree. It still
appears in its expected place in the component tree (in React
DevTools), but its actual DOM position is completely different.
Using Portals for Modals
The most common use case for Portals is modals:
import { useState } from "react";
import { createPortal } from "react-dom";
function Modal({ onClose, children }) {
// Bir modal, Portal'ın en yaygın kullanım alanıdır -- modal'ın CSS'i
// (position: fixed, z-index) sayfanın geri kalanının ÜSTÜNDE görünmesini
// sağlar, ama gerçek DOM konumu ("bir kartın içinde" gibi) bunu bazen
// engelleyebilir (overflow: hidden gibi). Portal, modal'ı doğrudan
// `document.body`'ye render ederek bu sorunu ORTADAN KALDIRIR.
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(event) => event.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>,
document.body,
);
}
function ModalWithPortalExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<Modal onClose={() => setIsOpen(false)}>
<p>This is a modal, rendered outside the normal DOM tree.</p>
</Modal>
)}
</div>
);
}
A modal's CSS (position: fixed, a high z-index) needs to make it
appear ABOVE the rest of the page -- but the modal's actual DOM
position (say, inside a card with overflow: hidden) can sometimes
prevent that. A Portal ELIMINATES this problem by rendering the modal
directly into document.body.
Event Bubbling: A Portal's Surprising Behavior
The most important (and most surprising) property of Portals is how events behave:
import { useState } from "react";
import { createPortal } from "react-dom";
function Popup() {
return createPortal(<button>Click me (rendered in document.body)</button>, document.body);
}
function EventBubblingThroughPortalExample() {
const [clicks, setClicks] = useState(0);
return (
// ÖNEMLİ: Popup, DOM'da bu <div>'in DIŞINDA (document.body'de)
// render ediliyor. Ama içindeki <button>'a tıklandığında, onClick
// yine de BURADA (React ağacındaki gerçek konumunda) çalışır --
// React, event'leri gerçek DOM ağacına göre değil, KENDİ component
// ağacına göre "bubble" ettirir. Bu, Portal'ların en şaşırtıcı ama en
// kullanışlı özelliği.
<div onClick={() => setClicks(clicks + 1)}>
<p>Clicks: {clicks}</p>
<Popup />
</div>
);
}
Popup renders OUTSIDE the outer <div> in the DOM (into
document.body). But clicking the button inside it still causes
onClick to bubble up to the outer <div> -- React propagates events
according to its OWN component tree, not the actual DOM tree. This is
the most important behavior to know when using Portals.
Setting Up a Portal Target
Instead of document.body, a dedicated target is usually used:
import { createPortal } from "react-dom";
function Tooltip({ text }) {
// document.body yerine, index.html'de özel olarak ayrılmış bir hedef
// kullanmak daha yaygındır -- örneğin <div id="tooltip-root"></div>,
// uygulamanın #root'una KARDEŞ (sibling) olarak eklenir. Bu, portal
// içeriğinin kendi stillerini/konumunu yönetmesini kolaylaştırır.
const target = document.getElementById("tooltip-root");
if (!target) {
return null;
}
return createPortal(<span className="tooltip">{text}</span>, target);
}
function PortalTargetSetupExample() {
return (
<div>
<p>Hover for more info</p>
<Tooltip text="This tooltip lives in its own DOM node." />
</div>
);
}
Adding something like <div id="tooltip-root"></div> as a SIBLING to
the app's #root in index.html is common practice -- it makes it
easier for the portal's content to manage its own styles and
positioning.
Summary and Glossary
createPortal(child, container) renders a component to a different DOM
node while KEEPING its position in the React tree -- the most common
uses are modals, tooltips, and dropdowns (to avoid CSS properties like
overflow: hidden on ancestor elements). Events bubble according to
React's component tree, not the actual DOM position -- this lets us
keep using Portals like normal components.
Glossary
Portal — A mechanism for rendering a component to a different DOM node while preserving its position in the React tree.
Event Bubbling — An event propagating upward from the element it was triggered on, through its ancestor elements.
Practical Project
There's a real, runnable example project that brings together the
concepts from this category (React Performance, Error Boundaries, Lazy
Loading & Code Splitting, Suspense, Portals):
Advanced React Demo
-- an application showing a course list optimized with React.memo, an
Error Boundary, a detail panel code-split with React.lazy +
Suspense, and a Portal modal, all working together.
You can download it and run it yourself, and read through the code line by line:
git clone https://github.com/cdurgun/react-course-projects.git
cd react-course-projects
npm install
cd projects/advanced-react
npm run dev
The react-course-projects repo uses npm workspaces -- npm install
only needs to run once, at the repo root, and every project folder
shares the same dependencies (no separate node_modules per folder). If
you've already run npm install at the root, you can just
cd react-course-projects/projects/advanced-react and run npm run dev.