Intersection Observer in React
Tags: frontend, react, typescript, webapi
Published: 2022-09-10 04:49:43

---
title: 'Intersection Observer in React'
metaTitle: 'Intersection Observer in React'
metaDesc: 'Intersection Observer in React'
tags:
  - frontend
  - react
  - typescript
  - webapi
---
## [#](#the-useintersectionobserver-hook) The `useIntersectionObserver` hook

This React Hook can be used to detect visibility of a component on the viewport using the :link: [IntersectionObserver API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_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:

```typescript

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

```

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:

```typescript

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

```

Finally, our hook returns an object with two properties:

```typescript

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

```

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

```typescript

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);
  ...
};

```

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

:bangbang: These are used to set the root and observable nodes for intersection observer.

```typescript

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]
);

```

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:

```typescript

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]);

```

## [#](#using-the-intersection-observer-hook) Using the intersection observer hook

### Define observer callback

```typescript

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

```

### Call `useIntersectionObserver` to get observable and root callbacks

```typescript

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

```

### Attach observable to observable nodes

```typescript

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

```

### Attach root to root node

```typescript

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

```

## [#](#references) References

:link: [Online Playground](https://stackblitz.com/edit/react-ts-be3pt8?file=App.tsx)

:star_struck: Scroll down on the browser in the playground to see the animation.