React Fragment ve Portal Kullanımı

React Fragment ve Portal Kullanımı
React'te bileşen yapısını düzenlerken sıkça karşılaşılan iki önemli kavram Fragment ve Portal'dır. Fragment, gereksiz DOM düğümlerinden kaçınmanızı sağlarken, Portal bileşenleri DOM hiyerarşisinin dışına render etmenize olanak tanır. Bu yazıda her iki kavramı derinlemesine inceleyeceğiz.
React Fragment Nedir?
React bileşenleri tek bir kök eleman döndürmelidir. Fragment, ekstra bir DOM düğümü oluşturmadan birden fazla elemanı gruplamanızı sağlar. Bu, gereksiz <div> sarmalayıcılardan kurtulmanın yoludur.
import { Fragment } from 'react';
// Sorun: Gereksiz div wrapper
function TableRow() {
return (
<div> {/* Bu div table yapısını bozar! */}
<td>İsim</td>
<td>Yaş</td>
</div>
);
}
// Çözüm: Fragment kullanımı
function TableRow() {
return (
<Fragment>
<td>İsim</td>
<td>Yaş</td>
</Fragment>
);
}
// Kısa sözdizimi
function TableRow() {
return (
<>
<td>İsim</td>
<td>Yaş</td>
</>
);
}
Fragment ile Key Kullanımı
Bir listeyi map ile render ederken Fragment'e key prop'u vermek gerekebilir. Bu durumda kısa sözdizimi (<></>) kullanılamaz, tam Fragment sözdizimi gerekir.
function GlossaryList({ items }) {
return (
<dl>
{items.map((item) => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}
// Tablo satırlarında Fragment
function UserTable({ users }) {
return (
<table>
<tbody>
{users.map((user) => (
<Fragment key={user.id}>
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
{user.notes && (
<tr>
<td colSpan={2} className="notes">{user.notes}</td>
</tr>
)}
</Fragment>
))}
</tbody>
</table>
);
}
Fragment Kullanım Senaryoları
- Tablo yapıları:
<tr>içinde sadece<td>olmalı, div eklenemez - Flex/Grid layout: Gereksiz wrapper, layout'u bozabilir
- CSS Selectors: Ekstra div,
:first-childgibi selector'ları bozar - Koşullu render: Birden fazla elemanı koşullu olarak gösterme
- Semantik HTML: Anlamsız div'lerden kaçınma
// Koşullu render ile Fragment
function UserInfo({ user, isAdmin }) {
return (
<div className="user-info">
<span>{user.name}</span>
{isAdmin && (
<>
<span className="badge">Admin</span>
<button>Ayarlar</button>
</>
)}
</div>
);
}
React Portal Nedir?
Portal, bir bileşenin çıktısını DOM ağacının farklı bir noktasına render etmenizi sağlar. Bileşen, React bileşen ağacında aynı yerde kalır (context, event bubbling korunur), ancak DOM çıktısı belirtilen hedefe eklenir.
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>
×
</button>
{children}
</div>
</div>,
document.getElementById('modal-root')
);
}
HTML dosyanızda portal hedefi olarak bir eleman eklemeyi unutmayın:
<!-- index.html -->
<body>
<div id="root"></div>
<div id="modal-root"></div>
<div id="tooltip-root"></div>
</body>
Portal ile Tooltip Bileşeni
import { createPortal } from 'react-dom';
import { useState, useRef, useEffect } from 'react';
function Tooltip({ children, text }) {
const [isVisible, setIsVisible] = useState(false);
const [position, setPosition] = useState({ top: 0, left: 0 });
const triggerRef = useRef(null);
useEffect(() => {
if (isVisible && triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
setPosition({
top: rect.top - 8 + window.scrollY,
left: rect.left + rect.width / 2 + window.scrollX,
});
}
}, [isVisible]);
return (
<>
<span
ref={triggerRef}
onMouseEnter={() => setIsVisible(true)}
onMouseLeave={() => setIsVisible(false)}
>
{children}
</span>
{isVisible &&
createPortal(
<div
className="tooltip"
style={{
position: 'absolute',
top: position.top,
left: position.left,
transform: 'translate(-50%, -100%)',
}}
>
{text}
</div>,
document.body
)}
</>
);
}
// Kullanım
<p>
Bu kelimenin üzerine gelin:{' '}
<Tooltip text="Bu bir tooltip!">
<strong>hover me</strong>
</Tooltip>
</p>
Portal'da Event Bubbling
Portal ile render edilen bileşenler DOM'da farklı bir yerde olsa bile, React event'leri bileşen ağacına göre bubble eder. Bu çok önemli bir özelliktir:
function Parent() {
const handleClick = () => {
console.log('Parent click tetiklendi!');
// Portal içindeki buton tıklandığında da çalışır
};
return (
<div onClick={handleClick}>
<h2>Parent Bileşen</h2>
<PortalChild />
</div>
);
}
function PortalChild() {
return createPortal(
<button>Portal'daki Buton</button>,
document.getElementById('portal-root')
);
}
// Butona tıklandığında Parent'ın onClick'i de tetiklenir!
event.stopPropagation() kullanırken dikkatli olun. React event bubbling'i DOM hiyerarşisine değil, React bileşen ağacına göre çalışır. Bu, beklenmedik davranışlara yol açabilir.
Portal ile Notification Sistemi
import { createPortal } from 'react-dom';
import { useState, useCallback } from 'react';
function useNotification() {
const [notifications, setNotifications] = useState([]);
const addNotification = useCallback((message, type = 'info') => {
const id = Date.now();
setNotifications((prev) => [...prev, { id, message, type }]);
setTimeout(() => {
setNotifications((prev) => prev.filter((n) => n.id !== id));
}, 3000);
}, []);
const NotificationContainer = () =>
createPortal(
<div className="notification-container">
{notifications.map((notif) => (
<div key={notif.id} className={`notification notification-${notif.type}`}>
{notif.message}
</div>
))}
</div>,
document.body
);
return { addNotification, NotificationContainer };
}
// Kullanım
function App() {
const { addNotification, NotificationContainer } = useNotification();
return (
<div>
<button onClick={() => addNotification('Başarılı!', 'success')}>
Bildirim Göster
</button>
<NotificationContainer />
</div>
);
}
Server-Side Rendering ve Portal
SSR ortamlarında document nesnesi mevcut değildir. Portal kullanırken bu durumu göz önünde bulundurmalısınız:
function SafePortal({ children, containerId }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
const container = document.getElementById(containerId);
if (!container) return null;
return createPortal(children, container);
}
aria-modal, role="dialog" ve focus trap uygulamayı unutmayın.
Sonuç
React Fragment, gereksiz DOM düğümlerinden kaçınarak temiz ve semantik HTML yapıları oluşturmanızı sağlar. Portal ise bileşenleri DOM hiyerarşisinin dışına render ederek modal, tooltip, dropdown ve bildirim gibi UI elemanlarını doğru şekilde konumlandırmanıza olanak tanır. Her iki özellik de React'in temel yapı taşlarından olup, günlük geliştirmede sıklıkla kullanılır. Fragment'i gereksiz wrapper'lardan kurtulmak, Portal'ı ise overflow ve z-index sorunlarını çözmek için değerlendirin.