Using Intersection Observer API in React
Tags: webdev, javascript, react, typescript
Published: 2023-10-16 12:43:05

This article is about using the Intersection Observer API to enhance the visual experience of web applications. We will see the usage of this API in both HTML/CSS projects and React applications.

## What is Intersection Observer API? (with a working example)

The [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) provides us with a way to take some action asynchronously whenever a particular element that we are observing comes or goes out from the viewport / or a specified container. Let's understand it using an example:

```javascript
//used in the codesandbox(Vanilla js) below:
const supportContainer = document.querySelector(".supporting");
const sectObserver = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      entry.target.classList.toggle("blockAnimation", entry.isIntersecting);
    });
  },
  {
    threshold: 0,
    rootMargin: "-100px"
  }
);

sectObserver.observe(supportContainer);
```

Let's break this down:

* We create a new Intersection Observer using `new IntersectionObserver(callback,options)` syntax. This callback is triggered when it initially attaches to the DOM element and each time the element intersects based on the specified options.
    
* Threshold value: A threshold of value 0 means that when any part of the observed element becomes visible, the `isIntersecting` property changes to true.
    
* RootMargin: rootMargin is used to set the margins of the root property. In our case, root is the viewport. We are using the rootMargin property of `-100px` because of the footer height.
    
* Callback: we pass a callback to the observer which toggles a blockAnimation class on our element based on the boolean value of `isIntersecting` property.
    

Codesandbox example using HTML, CSS, and Javascript:

Notice the slide-in effect of the supporting container in the codesandbox preview.

{% codesandbox 29fyv7 %}

## Infinite scroll in React:

Infinite scrolling is a popular feature in modern web applications. It is generally implemented using the intersection observer API. This section explains how to implement this feature, showcasing an example.



![Image showing custom infinite scroll example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ywm773q4qw8rbs36sy0q.png)

Link: [https://custom-infinite-scroll-react.netlify.app/](https://custom-infinite-scroll-react.netlify.app/)

### Idea:

The idea behind infinite scrolling is simple, let's understand it:

* We always observe the last item in the DOM using Intersection Observer API.
    
* Whenever the last item comes into the view (intersects), we call the API to fetch the next set of items.
    
* We disconnect the observer from the previous last item and connect it to the latest last item.
    

### Code:

```typescript
const App: React.FunctionComponent = () => {
  const [pageNumber, setPageNumber] = useState(1);
  const [todos, setTodos] = useState<todo[]>([]);
  const [loading, setLoading] = useState(false);
  const observer = useRef<IntersectionObserver>();

  const lastTodo = useCallback((node: Element | null) => {
    console.log(node);
    if (node != null) { // to check if the node exists
      if (observer.current) observer.current.disconnect();
      observer.current = new IntersectionObserver(
        (entries) => {
          const firstEntry = entries[0];
          if (firstEntry.isIntersecting) {
            setPageNumber((n) => n + 1);
          }
        },
        {
          threshold: 1,
        }
      );
      observer.current.observe(node);
    }
  }, []);

//this useEffect runs whenever the pageNumber gets changed
  useEffect(() => {
    fetchTodos(pageNumber, setTodos, setLoading);//fetches todos based on pageNumber
  }, [pageNumber]);

//JSX
  return (
    <div className="app">
      <header className="header">
        <h1>Custom Infinite Scroll</h1>
      </header>
      <main className="container">
        {todos.length
          ? todos.map((todo, index) => (
              <p
                className="todo"
                key={todo.id}
                ref={index === todos.length - 1 ? lastTodo : null}
              >
                {todo.title}
              </p>
            ))
          : ""}
        <div>{loading ? "Loading..." : ""}</div>
      </main>
      <footer className="footer">
        <p>Intersection Observer API</p>
      </footer>
    </div>
  );
};

export default App;
```

Let's break this down:

We declare three state variables:

1. `pageNumber`: to increase the page number when reaching the end of the page.
    
2. `todos`: an array of todos.
    
3. `loading`: to indicate the loading state.
    

We initialize an observer using `useRef` and `lastTodo` using `useCallback`.

We can pass either a `useRef` reference to the `ref` attribute or a function to the `ref` attribute (callback ref). In our case, we are using the latter.

`useCallback` is a better option in this case because the callback that we pass to `useCallback` gets called every time it attaches itself to a DOM element. Specifically, when the component using this callback as a ref mounts, this callback gets called with the element as an argument. When this component unmounts, the callback gets called with `null` as an argument.

So, whenever the callback gets called, here's what happens:

* We check if the node (element) is present or not; if it is, we proceed.
    
* If an observer is already observing an element, we disconnect the observer.
    
* We connect the observer to the new element.
    

When this element intersects our viewpoint, the `pageNumber` gets incremented. Whenever the `pageNumber` increases, the `useEffect` runs and fetches the next set of todos.

If you want to view the full code of the mentioned example: [https://github.com/Hardiegogo/intersection-observer-mock](https://github.com/Hardiegogo/intersection-observer-mock)

I hope you found this article helpful, see you in the next one!

Connect with me on [**Twitter**](https://twitter.com/Chirag__dev) | [**Linkedin**](https://www.linkedin.com/in/chirag-dev/) | [**Github**](https://github.com/Hardiegogo)

Happy Coding! 🎈🎈