Rreact.wiki
React Snippets

Intersection Observer Hook for Lazy Loading and Animations

Hookshookintersection-observerlazy-loadingperformanceanimations

A lightweight, reusable React hook that triggers callbacks when elements enter or leave the viewport—ideal for lazy-loading images or starting animations.

TSX
import { useState, useEffect, useRef } from 'react';
 
interface UseIntersectionObserverOptions
  extends Partial<IntersectionObserverInit> {
  threshold?: number | number[];
  rootMargin?: string;
}
 
export function useIntersectionObserver(
  options: UseIntersectionObserverOptions = {}
): [
  (node: Element | null) => void,
  boolean,
  IntersectionObserverEntry | null
] {
  const { threshold = 0, rootMargin = '0px', ...otherOptions } = options;
  const [isIntersecting, setIsIntersecting] = useState(false);
  const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);
  const targetRef = useRef<Element | null>(null);
 
  useEffect(() => {
    if (typeof window === 'undefined') return;
 
    const observer = new IntersectionObserver(
      ([observedEntry]) => {
        setIsIntersecting(observedEntry.isIntersecting);
        setEntry(observedEntry);
      },
      {
        threshold,
        rootMargin,
        ...otherOptions,
      }
    );
 
    observerRef.current = observer;
 
    return () => {
      observer.disconnect();
    };
  }, [threshold, rootMargin, otherOptions]);
 
  const setTarget = (node: Element | null) => {
    if (targetRef.current) {
      observerRef.current?.unobserve(targetRef.current);
    }
    if (node) {
      observerRef.current?.observe(node);
    }
    targetRef.current = node;
  };
 
  return [setTarget, isIntersecting, entry];
}

Use useIntersectionObserver by calling the returned setTarget ref callback on a DOM element (e.g., <img ref={setTarget} />). The hook returns [setTarget, isIntersecting, entry], letting you conditionally load images (srcdata-src) or trigger CSS animations when isIntersecting becomes true. It auto-cleans up the observer on unmount and supports custom threshold/rootMargin. ⚠️ Avoid re-creating the hook’s options object on every render—pass stable values (e.g., memoize with useMemo if needed). Example: const [ref, inView] = useIntersectionObserver({ threshold: 0.1 }); <div ref={ref}>{inView ? <AnimatedContent /> : <Placeholder />}</div>.

Intersection Observer Hook for Lazy Loading and Animations — react.wiki