Skip to content

useIntersectionObserver

Saurav-TB-Pandey edited this page Aug 10, 2026 · 2 revisions

useIntersectionObserver

Detects whether a specific DOM element is currently visible in the user's viewport. It is highly optimized for performance and is ideal for lazy-loading images, triggering infinite scrolling, or firing entrance animations without janky scroll-event listeners.

Usage Examples

Basic

Detecting if a footer element has entered the screen.

import { useRef } from "react";
import { useIntersectionObserver } from "react-hook-lab";

function LazyFooter() {
  const footerRef = useRef<HTMLDivElement>(null);
  const { isIntersecting } = useIntersectionObserver(footerRef);

  return (
    <div ref={footerRef} style={{ height: 200, background: isIntersecting ? "green" : "gray" }}>
      {isIntersecting ? "I am visible!" : "Scroll down to see me"}
    </div>
  );
}

Common

Using freezeOnceVisible for lazy loading images or entrance animations. Once the element appears on screen, it stays "visible" forever, stopping the observer and saving CPU cycles.

import { useRef } from "react";
import { useIntersectionObserver } from "react-hook-lab";

function FadeInSection({ children }: { children: React.ReactNode }) {
  const sectionRef = useRef<HTMLDivElement>(null);
  
  // Triggers when 20% of the element is visible, and then freezes
  const { isIntersecting } = useIntersectionObserver(sectionRef, {
    threshold: 0.2,
    freezeOnceVisible: true,
  });

  return (
    <div 
      ref={sectionRef} 
      style={{
        opacity: isIntersecting ? 1 : 0,
        transform: isIntersecting ? "translateY(0)" : "translateY(50px)",
        transition: "all 0.6s ease-out"
      }}
    >
      {children}
    </div>
  );
}

Advanced

Using rootMargin to preemptively load data before the user actually scrolls to the element (perfect for infinite scroll).

import { useRef, useEffect } from "react";
import { useIntersectionObserver } from "react-hook-lab";

function InfiniteFeed({ loadMore }: { loadMore: () => void }) {
  const loaderRef = useRef<HTMLDivElement>(null);
  
  // Triggers when the element is 500px AWAY from entering the screen
  const { isIntersecting } = useIntersectionObserver(loaderRef, {
    rootMargin: "500px 0px"
  });

  useEffect(() => {
    if (isIntersecting) {
      loadMore();
    }
  }, [isIntersecting, loadMore]);

  return (
    <div>
      {/* ... Feed Items ... */}
      <div ref={loaderRef}>Loading more posts...</div>
    </div>
  );
}

API Reference

Parameters

  • ref
    • Type: RefObject<HTMLElement>
    • Description: The React ref attached to the DOM element you want to observe.
  • options (Optional)
    • Type: UseIntersectionObserverOptions
    • Description: Configuration object extending standard IntersectionObserverInit.
export interface UseIntersectionObserverOptions extends IntersectionObserverInit {
  freezeOnceVisible?: boolean; // If true, disconnects observer after first intersection
}

Return Object

Returns an object of type UseIntersectionObserverReturn containing:

  • isIntersecting (boolean): A strict boolean representing if the element is currently visible (based on your threshold).
  • entry (IntersectionObserverEntry | undefined): The raw, underlying Intersection Observer entry containing detailed metrics like boundingClientRect, intersectionRatio, etc.

Core Working

Under the hood, useIntersectionObserver utilizes the native browser IntersectionObserver API. When the component mounts, it creates a new observer instance targeted at ref.current. When the element's visibility crosses the specified threshold, the browser fires a callback that updates the React state with the new IntersectionObserverEntry.

If freezeOnceVisible: true is passed, the hook actively checks if (entry?.isIntersecting). The moment this becomes true, the useEffect cleans up, calls observer.disconnect(), and stops observing entirely. This ensures that entrance animations or lazy-loaders consume zero performance after their initial trigger.

Clone this wiki locally