1. 📖 Introduction
Not every React feature is about state or rendering logic — some solve structural problems instead. Fragments let you group elements without adding extra DOM nodes, Portals let you render children into a different part of the DOM tree entirely, and Refs give you a direct escape hatch to imperatively touch DOM nodes or component instances. This tutorial covers all three in depth, including real-world patterns like modals, tooltips, and focus management.
2. 🧩 React Fragments
<Fragment> lets a component return multiple elements without wrapping them in an extra DOM node like a <div>. This keeps your rendered markup clean, especially important for layouts relying on flex or grid from a direct parent.
FragmentBasic.jsx
import { Fragment } from "react";
function UserInfo() {
return (
<Fragment>
<h2>Jane Doe</h2>
<p>jane@example.com</p>
</Fragment>
);
}3. ✂️ Short Fragment Syntax
The shorthand <></> syntax is equivalent to <Fragment></Fragment>, but more concise — it's the version used throughout most modern React code.
ShortFragment.jsx
function UserInfo() {
return (
<>
<h2>Jane Doe</h2>
<p>jane@example.com</p>
</>
);
}Caution
4. 🔑 Keyed Fragments
When rendering a list of fragments — each containing multiple sibling elements — the full <Fragment key={...}> syntax is required, since keys can only be applied to the explicit component form.
KeyedFragment.jsx
function Glossary({ terms }) {
return (
<dl>
{terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}5. 🤔 When to Use Fragments
- Returning multiple sibling elements from a component without an extra wrapper
- Preserving valid HTML structure, like table rows or list items, where an extra <div> would be invalid
- Avoiding unnecessary DOM nesting that could interfere with CSS layout (like flex or grid children)
- Rendering a keyed group of elements inside a .map() call
6. ❓ What are Portals?
A Portal renders a component's children into a DOM node outside its parent's DOM hierarchy — while keeping it in the same place in the React component tree for context, state, and event purposes.
7. 🏗️ Creating Portals
Portals are created with createPortal from react-dom, which takes the JSX to render and a target DOM node.
CreatePortal.jsx
import { createPortal } from "react-dom";
function Modal({ children }) {
return createPortal(
<div className="modal-overlay">
<div className="modal-content">{children}</div>
</div>,
document.getElementById("modal-root")
);
}Note
8. 🎯 Portal Rendering
Even though a Portal's DOM output lives elsewhere on the page, it still participates fully in React's normal render cycle — receiving props, using hooks, and re-rendering just like any other component.
PortalRendering.jsx
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="app">
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<Modal>
<p>This content renders in #modal-root, not inside .app</p>
</Modal>
)}
</div>
);
}9. 🫧 Portal Event Bubbling
Despite rendering into a different DOM location, events dispatched from inside a Portal still bubble up through the React tree as if the Portal were rendered in its logical position — not its physical DOM position.
PortalBubbling.jsx
function App() {
return (
<div onClick={() => console.log("Parent div clicked")}>
<Modal>
<button onClick={() => console.log("Button clicked")}>Click me</button>
</Modal>
</div>
);
}
// Clicking the button logs both messages, even though Modal's
// DOM node lives outside the parent div in the actual document.Important
10. 🎭 Common Portal Use Cases
Portals solve a specific visual problem: content that needs to appear above everything else or outside a clipped/overflow container, regardless of where it's logically defined in your component tree.
11. 🪟 Modals
Modals are the textbook Portal use case — they need to render above the entire page, unaffected by any parent's overflow: hidden or z-index stacking context.
ModalExample.jsx
function ConfirmModal({ onConfirm, onCancel }) {
return createPortal(
<div className="modal-overlay">
<div className="modal-box" role="dialog" aria-modal="true">
<p>Are you sure you want to delete this item?</p>
<button onClick={onConfirm}>Delete</button>
<button onClick={onCancel}>Cancel</button>
</div>
</div>,
document.getElementById("modal-root")
);
}12. 💬 Dialogs
Dialogs follow the same Portal pattern as modals but are often paired with the native <dialog> element for built-in accessibility features like focus trapping and Escape-to-close behavior.
13. 💡 Tooltips
Tooltips use Portals to escape a parent's clipping context, ensuring the tooltip is never cut off by a container with overflow: hidden.
TooltipExample.jsx
function Tooltip({ text, children }) {
const [show, setShow] = useState(false);
return (
<span onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
{children}
{show &&
createPortal(
<div className="tooltip">{text}</div>,
document.getElementById("tooltip-root")
)}
</span>
);
}14. 🔽 Dropdown Menus
Similarly to tooltips, dropdown menus rendered via a Portal avoid being visually clipped by a scrollable or overflow-constrained parent container, like a table cell or card.
15. 🍞 Toast Notifications
Toast notifications are almost always rendered at the very top of the DOM, via a single shared Portal target, so they consistently appear above all other page content regardless of which component triggered them.
ToastExample.jsx
function ToastContainer({ toasts }) {
return createPortal(
<div className="toast-container">
{toasts.map((toast) => (
<div key={toast.id} className="toast">{toast.message}</div>
))}
</div>,
document.getElementById("toast-root")
);
}16. 🪝 useRef
useRef creates a mutable object that persists across renders without triggering a re-render when it changes — commonly used to access DOM nodes directly, or to store any value that shouldn't participate in the render cycle.
UseRefBasic.jsx
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus the input</button>
</>
);
}17. 🏗️ Creating Refs
A ref is created with useRef(initialValue), returning an object of the shape { current: initialValue }. Its current property can be read or mutated freely without causing a re-render.
RefCreation.jsx
const countRef = useRef(0); // { current: 0 }
countRef.current += 1; // mutate directly, no re-render triggered18. 🎯 Accessing DOM Elements
Passing a ref as the ref prop on a native element (like <input> or <div>) gives you direct access to the underlying DOM node once it mounts.
DomRefAccess.jsx
function VideoPlayer() {
const videoRef = useRef(null);
const play = () => videoRef.current.play();
return (
<>
<video ref={videoRef} src="/demo.mp4" />
<button onClick={play}>Play</button>
</>
);
}19. 🔒 Mutable References
Beyond DOM access, refs are the standard way to store any value that needs to persist across renders but shouldn't cause a re-render when updated — like a timer ID or a previous prop value.
MutableRef.jsx
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => console.log("tick"), 1000);
};
const stop = () => clearInterval(intervalRef.current);
return (
<>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</>
);
}20. ➡️ Forwarding Refs
By default, a ref placed on a custom component doesn't automatically reach the underlying DOM node inside it — the component must explicitly forward it.
21. 🔀 forwardRef
forwardRef lets a custom component accept a ref and pass it down to one of its own internal elements, most often a native DOM node.
ForwardRefExample.jsx
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
return <input ref={ref} className="fancy-input" {...props} />;
});
function Form() {
const inputRef = useRef(null);
return <FancyInput ref={inputRef} placeholder="Type here" />;
}22. 🎛️ useImperativeHandle
useImperativeHandle customizes exactly what a parent sees when it accesses a forwarded ref — exposing a curated set of methods instead of the raw DOM node.
UseImperativeHandle.jsx
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => (inputRef.current.value = ""),
}));
return <input ref={inputRef} {...props} />;
});
// Parent can now only call fancyInputRef.current.focus() or .clear()
// -- not access the raw DOM node directly.Tip
23. 📞 Callback Refs
Instead of useRef, a ref prop can also accept a function, called with the DOM node when it mounts (and with null when it unmounts) — useful when you need to run logic exactly at that moment.
CallbackRef.jsx
function MeasuredBox() {
const [height, setHeight] = useState(0);
const measureRef = (node) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
};
return <div ref={measureRef}>Content</div>;
}24. 🖐️ Imperative DOM Manipulation
Refs are the sanctioned "escape hatch" for the rare cases where you need to directly command a DOM node — playing a video, triggering a native animation, or calling an imperative API a third-party library exposes.
Caution
25. 🎯 Focus Management
Refs are commonly used to move keyboard focus programmatically — for example, focusing the first field of a form when it opens, or returning focus to a trigger button after closing a modal.
FocusManagement.jsx
function Modal({ onClose }) {
const closeButtonRef = useRef(null);
useEffect(() => {
closeButtonRef.current?.focus();
}, []);
return (
<div role="dialog" aria-modal="true">
<button ref={closeButtonRef} onClick={onClose}>Close</button>
</div>
);
}26. 📜 Scroll Management
Refs let you programmatically scroll an element into view or to a specific position, using the native scrollIntoView or scrollTo DOM methods.
ScrollManagement.jsx
function ChatWindow({ messages }) {
const bottomRef = useRef(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
return (
<div>
{messages.map((m) => <p key={m.id}>{m.text}</p>)}
<div ref={bottomRef} />
</div>
);
}27. 📏 Measuring DOM Elements
Refs combined with getBoundingClientRect() let you read an element's actual rendered size or position — information that isn't otherwise available from props or state.
MeasureElement.jsx
function useElementWidth() {
const ref = useRef(null);
const [width, setWidth] = useState(0);
useEffect(() => {
if (ref.current) {
setWidth(ref.current.getBoundingClientRect().width);
}
}, []);
return [ref, width];
}28. 🔌 Third-Party Library Integration
Many non-React libraries (chart libraries, map widgets, rich text editors) expect a raw DOM node to initialize themselves against — refs are the standard bridge for wiring these into a React component.
ThirdPartyIntegration.jsx
function ChartWidget({ data }) {
const containerRef = useRef(null);
useEffect(() => {
const chart = new ThirdPartyChart(containerRef.current, { data });
return () => chart.destroy();
}, [data]);
return <div ref={containerRef} />;
}29. 🏆 Ref Best Practices
- Use refs for direct DOM access and imperative actions, not as a substitute for state that should drive rendering
- Prefer useImperativeHandle over exposing a raw DOM node when building a reusable component API
- Always check a ref is not null before use, since it starts as null before the first render commits
- Clean up any imperative side effects (like third-party library instances) tied to a ref inside a useEffect cleanup function
30. ⚠️ Common Mistakes
- Reading ref.current during rendering, before it has been set by the browser committing the DOM
- Using a ref to store data that should actually be state, causing the UI not to update when it changes
- Forgetting to forward a ref through a custom component, so the parent's ref.current stays null
- Not providing a Portal's target DOM node in HTML before rendering, causing createPortal to throw
- Assuming Portal content is isolated from event bubbling, when in fact it bubbles through the React tree normally
Danger
31. 💬 Frequently Asked Questions
Do I need a Fragment if a component only returns one element?
No — Fragments are only needed when returning multiple sibling elements. A single root element doesn't require one.
Why use a Portal instead of just placing the modal higher in the component tree?
Moving a component higher in the React tree doesn't change where its output lands in the actual DOM — a parent's overflow: hidden or stacking context can still clip it. Portals solve this at the DOM level while keeping the component logically nested where it makes sense.
Can I use a ref instead of state to trigger a UI update?
No — updating ref.current does not cause a re-render. If a value needs to be reflected in the rendered UI, it belongs in state, not a ref.