DEV Community

Manoj Kumar Patra
Manoj Kumar Patra

Posted on

Intersection Observer in React

# The useIntersectionObserver hook

This React Hook can be used to detect visibility of a component on the viewport using the 🔗 IntersectionObserver API natively present in the browser.

Some of the use cases include:

  1. Lazy-loading of images
  2. Infinite scrolling
  3. Start animations

Our hook will take two arguments of the following type:


{
  options?: Omit<IntersectionObserverInit, 'root'>;
  observerCallback: IntersectionObserverCallback;
}

Enter fullscreen mode Exit fullscreen mode

We will use observerCallback to define behaviour on intersection with viewport. It has a type of IntersectionObserverCallback which is defined as an interface as follows:


interface IntersectionObserverCallback {
    (entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;
}

Enter fullscreen mode Exit fullscreen mode

Finally, our hook returns an object with two properties:


{
  observable: (node: Element) => void;
  root: (node: Element) => void;
}

Enter fullscreen mode Exit fullscreen mode

We define three reference variables:

  1. nodeRefs - To hold references for observable nodes as an array of reference objects
  2. observerRef - To hold reference for intersection observer
  3. rootRef - To hold reference for root node with which observables intersect

export function useIntersectionObserver({
  options = { rootMargin: '0px', threshold: [0] },
  observerCallback,
}: {
  options?: Omit<IntersectionObserverInit, 'root'>;
  observerCallback: IntersectionObserverCallback;
}) {
  const nodeRefs = useRef<Element[]>([]);
  const rootRef = useRef<Element>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);
  ...
};

Enter fullscreen mode Exit fullscreen mode

Then, we define rootRefCallback and observableRefCallback mapped to root and observable respectively as follows:

‼️ These are used to set the root and observable nodes for intersection observer.


const observableRefCallback = useCallback<(node: Element) => void>(
  (node) => {
    nodeRefs.current.push(node);
    initializeObserver();
  },
  [initializeObserver]
);

const rootRefCallback = useCallback<(node: Element) => void>(
  (rootNode) => {
    rootRef.current = rootNode;
    initializeObserver();
  },
  [initializeObserver]
);

Enter fullscreen mode Exit fullscreen mode

In both cases, once the root and observable nodes are set, we initialize the intersection observer with function initializeObserver. This function basically resets the intersection observer as follows:


const unobserve = useCallback(() => {
  // stops watching all of its target elements for visibility changes.
  observerRef.current?.disconnect();
  // sets to null
  observerRef.current = null;
}, []);

const observe = useCallback(() => {
  // If observable nodes exist
  const nodes = nodeRefs.current;
  if (nodes.length > 0) {
    const root = rootRef.current;
    const { rootMargin, threshold } = options;
    // Create a new IntersectionObserver instance
    const observer = new IntersectionObserver(observerCallback, {
      root,
      rootMargin,
      threshold,
    });
    // Observe all observable nodes
    nodes.forEach((node) => observer.observe(node));
    // Set observer reference
    observerRef.current = observer;
  }
}, [options.rootMargin, options.threshold]);


const initializeObserver = useCallback(() => {
  unobserve();
  observe();
}, [observe, unobserve]);

Enter fullscreen mode Exit fullscreen mode

# Using the intersection observer hook

Define observer callback


const observerCallback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
  entries.forEach((entry) => {
    ...
  });
};

Enter fullscreen mode Exit fullscreen mode

Call useIntersectionObserver to get observable and root callbacks


const { observable, root } = useIntersectionObserver({
  observerCallback,
});

Enter fullscreen mode Exit fullscreen mode

Attach observable to observable nodes


React.useEffect(() => {
  document.querySelectorAll('.anim').forEach(observable);
}, []);

Enter fullscreen mode Exit fullscreen mode

Attach root to root node


<div ref={root}>
  <div className="anim">...</div>
  <div className="anim">...</div>
  <div className="anim">...</div>
</div>

Enter fullscreen mode Exit fullscreen mode

# References

🔗 Online Playground

🤩 Scroll down on the browser in the playground to see the animation.

Top comments (0)