Skip to main content
react-md

useIntersectionObserver

function useIntersectionObserver<E extends HTMLElement>(
  options: IntersectionObserverHookOptions<E>
): RefCallback<E>;

The useIntersectionObserver hook can be used to interact with the Intersection Observer API within React components.

Example Usage

This example was forked from the Mozilla Simple Example and a demo is available here.

"use client";

import { Typography } from "@react-md/core/typography/Typography";
import { useIntersectionObserver } from "@react-md/core/useIntersectionObserver";
import { type ReactElement, useCallback, useState } from "react";

import styles from "./SimpleExample.module.scss";

const numSteps = 20;
const thresholds = Array.from({ length: numSteps }, (_, i) => i / numSteps);
thresholds.push(0);

const INCREASING = "rgba(40, 40, 190, ratio)";
const DECREASING = "rgba(190, 40, 40, ratio)";

export default function SimpleExample(): ReactElement {
  const [{ ratio, increasing }, setState] = useState({
    ratio: 0.0,
    increasing: true,
  });

  const targetRef = useIntersectionObserver({
    threshold: thresholds,
    rootMargin: "0px",
    onUpdate: useCallback(([entry]) => {
      const { intersectionRatio } = entry;
      setState((prevState) => {
        return {
          ratio: intersectionRatio,
          increasing: intersectionRatio > prevState.ratio,
        };
      });
    }, []),
  });

  return (
    <>
      <Typography type="headline-4" margin="none" textAlign="center">
        Scroll Down
      </Typography>
      <div className={styles.container}>
        <div
          ref={targetRef}
          className={styles.box}
          style={{
            backgroundColor: (increasing ? INCREASING : DECREASING).replace(
              "ratio",
              `${ratio}`,
            ),
          }}
        >
          <div className={styles.vertical}>
            Welcome to <strong>The Box!</strong>
          </div>
        </div>
      </div>
    </>
  );
}

Parameters

export interface IntersectionObserverHookOptions<E extends HTMLElement> {
  /**
   * An optional ref to merge with the ref returned by this hook.
   */
  ref?: Ref<E>;

  /**
   * **Must be wrapped in `useCallback` to prevent re-creating the
   * IntersectionObserver each render.**
   */
  onUpdate: (entries: readonly IntersectionObserverEntry[]) => void;

  /**
   * **Must be wrapped in `useCallback` to prevent re-creating the
   * IntersectionObserver each render.**
   *
   * If this is defined, the `ref` will be ignored along with the returned
   * ref.
   */
  getTargets?: () => readonly Element[];

  /**
   * This is the same as the normal `root` for an IntersectionObserverInit, but
   * also supports refs.
   */
  root?: RefObject<IntersectionObserverRoot> | IntersectionObserverRoot;

  /**
   * Set this to `true` if the intersection observer behavior should be
   * disabled.
   *
   * @defaultValue `false`
   */
  disabled?: boolean;

  /**
   * **When using a list of thresholds, they must either be defined outside of
   * the component or wrapped in a `useMemo` to prevent the IntersectionObserver
   * from being re-created each render.**
   *
   * @see {@link getThreshold}
   */
  threshold?: IntersectionObserverThreshold;

  /** @see {@link getRootMargin} */
  rootMargin?: IntersectionObserverRootMargin;

  /**
   * **Must be wrapped in `useCallback` to prevent re-creating the
   * IntersectionObserver each render.**
   *
   * This can be used to dynamically generate the `threshold` which is
   * generally useful if you need access to the DOM or do some expensive
   * computation.
   *
   * If this option is provided, `threshold`'s value will be ignored.
   */
  getThreshold?: () => IntersectionObserverThreshold;

  /**
   * **Must be wrapped in `useCallback` to prevent re-creating the
   * IntersectionObserver each render.**
   *
   * This can be used to dynamically generate the `rootMargin` which is
   * generally useful if you need access to the DOM.
   *
   * Note: If this option is provided, `rootMargin` will be ignored.
   */
  getRootMargin?: () => IntersectionObserverRootMargin;
}

getTargets

The getTargets option can be used to track multiple elements. When this option is provided, the hook ignores the value of the returned ref.

This is used for the useActiveHeadingId hook.

threshold

The threshold option can be used to define the intersection threshold. This value needs to be memoized to prevent re-creating the Intersection Observer during each render.

// Memoized by not having it during the render cycle
const threshold = [0, 0.25, 0.5, 0.75, 1];

function Example() {
  const targetRef = useIntersectionObserver({
    threshold,
    onUpdate: useCallback(([entry]) => {
      // do something
    }, []),
  });
}

// or just use useMemo
function Example() {
  const targetRef = useIntersectionObserver({
    threshold: useMemo(() => [0, 0.25, 0.5, 0.75, 1]),
    onUpdate: useCallback(([entry]) => {
      // do something
    }, []),
  });
}

getThreshold

This option can be used to dynamically generate a threshold and will override the threshold value.

const targetRef = useIntersectionObserver({
  getThreshold: useCallback(() => {
    // pretend some expensive computation
    return [0, 0.25, 0.5, 0.75, 1];
  }, []),
  onUpdate: useCallback(() => {
    // do something
  }, []),
});

getRootMargin

This option can be used to dynamically generate the rootMargin.

const nodeRef = useRef<HTMLElement>();
const targetRef = useIntersectionObserver({
  getRootMargin: useCallback(() => {
    return `-${nodeRef.current.offsetHeight - 1}px 0px 0px`;
  }, []),
  onUpdate: useCallback(() => {
    // do something
  }, []),
});

Returns

A RefCallback that needs to be passed to the DOM node to watch.

See Also