useIntersectionObserver
A hook for observing the intersection of an element (or array of elements) with the viewport
Source Code
import { useEffect, useState, RefObject, useRef } from "react";
type IntersectionCallback = (
isIntersecting: boolean,
entry: IntersectionObserverEntry
) => void;
interface UseIntersectionObserverOptions {
root?: RefObject<HTMLElement | null> | null;
threshold?: number | number[];
rootMargin?: string;
}
export const useIntersectionObserver = <T extends HTMLElement>(
{
threshold = 0,
root = null,
rootMargin = "0%",
}: UseIntersectionObserverOptions = {},
callback?: IntersectionCallback
) => {
const ref = useRef<T>(null);
const [state, setState] = useState<{
isIntersecting: boolean;
entry: IntersectionObserverEntry | undefined;
}>({
isIntersecting: false,
entry: undefined,
});
const callbackRef = useRef<IntersectionCallback | undefined>(callback);
callbackRef.current = callback;
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
const isIntersecting = entry.isIntersecting;
if (callbackRef.current) {
callbackRef.current(isIntersecting, entry);
} else {
setState({ isIntersecting, entry });
}
});
},
{ threshold, root: root?.current, rootMargin }
);
observer.observe(element);
return () => observer.disconnect();
}, [ref, root, rootMargin, threshold]);
return [ref, state] as const;
};
API Reference
Prop | Default | Type | Description |
---|---|---|---|
| - |
| The options to pass to the IntersectionObserver. |
| - |
| The callback to call when the element(s) intersect. |
The hook returns a tuple containing:
ref
(RefObject<HTMLElement>
): A ref object to attach to the element(s) you want to observe- An object containing:
isIntersecting
(boolean
): Whether the element is currently intersecting with the viewportentry
(IntersectionObserverEntry
): The full intersection observer entry with additional details
When a callback is provided, the states (isIntersecting
and entry
) are never updated. This provides flexibility in handling intersection events - you can either react to changes declaratively using the returned state, or use the callback for more performant, render-independent updates.
Examples
Basic
const Example = () => {
const [ref, { isIntersecting }] = useIntersectionObserver<HTMLDivElement>();
return (
<div ref={ref}>
{isIntersecting ? "Element is visible" : "Element is hidden"}
</div>
);
};
As Callback
const Example = () => {
const [ref] = useIntersectionObserver<HTMLDivElement>(
{ threshold: 0.5 },
(isIntersecting) => {
if (isIntersecting) {
window.dataLayer?.push({ event: "elementVisible" });
}
}
);
return <div ref={ref}>Element</div>;
};