Skip to main content

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.

Live: N  Frozen: N

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.

Target element
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.

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
childList mutations observed: 0
const listRef = useRef<HTMLUListElement>(null);
const [count, setCount] = useState(0);

useMutationObserver(listRef, () => setCount(c => c + 1), { childList: true });

<ul ref={listRef}>{items.map(...)}</ul>