usePortal
Create and manage portals for rendering content outside the component tree. Perfect for modals, tooltips, and overlays.
Live Demo
🔍 What's a Portal?
Portals render content outside the component tree, typically at the end of document.body. This is useful for modals, tooltips, and overlays that need to escape parent overflow/z-index constraints.
📝 Portal Events
No portal events yet. Try the buttons above!
Code Examples
Basic Modal
import { usePortal } from "@uiblock/hooks";
function Modal({ isOpen, onClose, children }) {
const { Portal } = usePortal({ enabled: isOpen });
if (!isOpen) return null;
return (
<Portal>
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
</Portal>
);
}With Custom Container
import { usePortal } from "@uiblock/hooks";
function Notification({ message }) {
const { Portal } = usePortal({
id: 'notification-portal', // Reuse existing element
enabled: true
});
return (
<Portal>
<div className="notification">
{message}
</div>
</Portal>
);
}Tooltip with Portal
import { usePortal } from "@uiblock/hooks";
import { useState } from "react";
function TooltipButton() {
const [showTooltip, setShowTooltip] = useState(false);
const { Portal } = usePortal({ enabled: showTooltip });
return (
<>
<button
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
Hover me
</button>
{showTooltip && (
<Portal>
<div className="tooltip">
This is a tooltip!
</div>
</Portal>
)}
</>
);
}Multiple Portals
import { usePortal } from "@uiblock/hooks";
function App() {
const { Portal: ModalPortal } = usePortal({ id: 'modal-root' });
const { Portal: TooltipPortal } = usePortal({ id: 'tooltip-root' });
const { Portal: NotificationPortal } = usePortal({ id: 'notification-root' });
return (
<>
<ModalPortal>
<div>Modal content</div>
</ModalPortal>
<TooltipPortal>
<div>Tooltip content</div>
</TooltipPortal>
<NotificationPortal>
<div>Notification content</div>
</NotificationPortal>
</>
);
}How It Works
Here's the implementation:
import { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
export function usePortal(options = {}) {
const { id, parent, enabled = true } = options
const [isMounted, setIsMounted] = useState(false)
const portalElementRef = useRef(null)
useEffect(() => {
if (!enabled) {
setIsMounted(false)
return
}
// Find or create portal element
let element = null
if (id) {
element = document.getElementById(id)
}
if (!element) {
element = document.createElement('div')
if (id) {
element.id = id
}
element.setAttribute('data-portal', 'true')
}
const parentElement = parent ?? document.body
const shouldAppend = !element.parentElement
if (shouldAppend) {
parentElement.appendChild(element)
}
portalElementRef.current = element
setIsMounted(true)
return () => {
// Only remove if we created it (no id provided) and it was appended
if (shouldAppend && !id && element && element.parentElement) {
element.parentElement.removeChild(element)
}
portalElementRef.current = null
setIsMounted(false)
}
}, [id, parent, enabled])
const Portal = ({ children }) => {
if (!isMounted || !portalElementRef.current) {
return null
}
return createPortal(children, portalElementRef.current)
}
return {
Portal,
portalElement: portalElementRef.current,
isMounted
}
}Key Features:
- Creates portal containers dynamically
- Reuses existing containers by ID
- Automatic cleanup on unmount
- Returns Portal component and mount status
- Renders content outside component tree
API Reference
Options
id?: stringID of the portal container element. If not provided, a new element will be created.
parent?: HTMLElementParent element to append the portal to (default: document.body)
enabled?: booleanWhether to mount the portal immediately (default: true)
Returns
Portal: ComponentComponent to render content into the portal
portalElement: HTMLElement | nullThe portal container element
isMounted: booleanWhether the portal is mounted
💡 Use Cases
- Modal dialogs that need to escape parent overflow
- Tooltips that need to appear above all content
- Toast notifications
- Dropdown menus with complex positioning
- Lightboxes and image galleries
- Context menus