Octocat

useIntersectionObserver

A hook for observing the intersection of an element (or array of elements) with the viewport

Source Code

import { type RefObject, useEffect, useRef, useState } 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();
  }, [root, rootMargin, threshold]);

  return [ref, state] as const;
};

API Reference

Prop Default Type Description
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>;
};
        

Previous

useElementTransition

Next

useMatchMedia