Observers
Thin, cleanup-safe wrappers around the browser's own Observer APIs.
useIntersectionObserver
Tracks whether an element is visible in the viewport via a callback ref, powered by the native IntersectionObserver API. Shown here side by side: the default (Live) toggles on every crossing, while freezeOnceVisible (Frozen) disconnects for good the first time it's seen — commonly used for lazy loading, entrance animations, and infinite-scroll triggers.
Scroll down — the target box becomes visible once it's 50% in view. Live keeps toggling as it enters/leaves the viewport; Frozen locks to Y the first time it's seen and stays that way.
const [liveRef, { isIntersecting: live }] = useIntersectionObserver({
threshold: 0.5,
});
const [frozenRef, { isIntersecting: frozen }] = useIntersectionObserver({
threshold: 0.5,
freezeOnceVisible: true,
});
const targetRef = useMergedRef(liveRef, frozenRef);
<div ref={targetRef}>...target...</div>;
useResizeObserver
Reports an element's own width/height as it's resized — the unprocessed primitive behind useResponsiveSize/useElementScroll/useElementPosition, for when you just want the size.
Drag the bottom-right corner of the box to resize it.
const [ref, size] = useResizeObserver<HTMLDivElement>();
<div ref={ref}>
{size?.width} x {size?.height}
</div>;
useMutationObserver
Watches a target (a ref, or a plain Node like document.head) for DOM mutations. The callback is read from a ref, so passing a fresh inline function every render doesn't tear down and resubscribe the observer.
- Item 1
const listRef = useRef<HTMLUListElement>(null);
const [count, setCount] = useState(0);
useMutationObserver(listRef, () => setCount(c => c + 1), { childList: true });
<ul ref={listRef}>{items.map(...)}</ul>