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

PropDefaultTypeDescription

options

-

{ threshold?: number | number[], root?: RefObject<HTMLElement | null> | null, rootMargin?: string }

The options to pass to the IntersectionObserver.

callback

-

(isIntersecting: boolean, entry?: IntersectionObserverEntry) => void

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 viewport
    • entry (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>;
};