DEV Community

Manoj Kumar Patra
Manoj Kumar Patra

Posted on

2

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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay