"use client"; import * as React from "react"; /** * Observes element visibility using the Intersection Observer API. * * @remarks * This hook creates an IntersectionObserver that watches the provided element * reference and returns the latest `IntersectionObserverEntry`. It's useful * for implementing lazy loading, infinite scroll, animations on scroll, and * tracking element visibility. * * The observer automatically disconnects when the component unmounts or when * the element reference changes. The hook is SSR-safe and returns `null` when * running on the server or when the observer is not yet initialized. * * @param ref - A React ref object pointing to the element to observe. * @param options - Optional IntersectionObserver configuration (threshold, root, rootMargin). * @returns The latest IntersectionObserverEntry or null if not intersecting yet. * * @example * ```tsx * function LazyImage({src, alt}: {src: string; alt: string}) { * const imageRef = useRef(null); * const entry = useIntersectionObserver(imageRef, {threshold: 0.1}); * * return ( * {alt} * ); * } * ``` * * @example * ```tsx * function AnimateOnScroll({children}: {children: React.ReactNode}) { * const ref = useRef(null); * const entry = useIntersectionObserver(ref, {threshold: 0.5}); * const isVisible = entry?.isIntersecting ?? false; * * return ( *
* {children} *
* ); * } * ``` */ export function useIntersectionObserver( ref: React.RefObject, options?: IntersectionObserverInit, ): IntersectionObserverEntry | null { const [entry, setEntry] = React.useState(null); const threshold = options?.threshold; const root = options?.root; const rootMargin = options?.rootMargin; React.useEffect(() => { const element = ref.current; // SSR safety: IntersectionObserver is not available on server if (globalThis.IntersectionObserver === undefined || !element) { return; } const observer = new globalThis.IntersectionObserver( ([observerEntry]) => { if (observerEntry) { setEntry(observerEntry); } }, {threshold, root, rootMargin}, ); observer.observe(element); return () => { observer.disconnect(); }; }, [ref, threshold, root, rootMargin]); return entry; }