Use the IntersectionObserver API in React
Tags: webdev, javascript, react, typescript
Published: 2024-03-28 01:42:33

In our previous post, we [covered](https://phuoc.ng/collection/intersection-observer-with-react/an-introduction-to-the-intersection-observer-api/) the basics of the IntersectionObserver API, including its syntax and common methods. The good news is that we don't need to rely on any external libraries to use this API with React. By using the built-in React hooks, we can handle all the intersection observer functionality with ease.

In this post, we'll dive into how to use the IntersectionObserver API in React.

## Checking if an element is partially visible

When working with React, it's not recommended to use DOM manipulation methods like `querySelector` and `querySelectorAll` to retrieve elements. Instead, we can use the `useRef` hook.

> If you're new to `useRef`, I recommend checking out this [series](https://phuoc.ng/collection/react-ref/) which covers basic and advanced usage of React refs with real-world examples.

To start, we create a ref using the `useRef` hook to reference the specific DOM element we want to observe for visibility changes. We attach the target element to the ref using the `ref` attribute.

Then, we set up a state variable called `isVisible` using the `useState` hook with an initial value of `false`.

```tsx
const elementRef = React.useRef();
const [isVisible, setIsVisible] = React.useState(false);

// Render
return (
    <div ref={elementRef}>...</div>
);
```

Next, we're going to use the `useEffect` hook to set up an `IntersectionObserver`. This is a browser API that lets us know when an element becomes visible or hidden on the page. But before we can use the API, we need to get the element we want to observe.

To do this, we used the `useRef` hook to create a reference to the element. Then, we access the `current` property of the reference to get the actual element.

Once we have the element, we create an observer instance using the `IntersectionObserver` constructor. We pass in a callback function that will be called whenever the element intersects with the viewport.

The callback function receives an array of `entries`, but we only care about the first one. So, we use array destructuring to get it. This `entry` object contains information about whether or not the element is currently visible.

If the element is visible, we set our `isVisible` state variable to `true`. If it's not visible, we set `isVisible` to `false`. Finally, we call the observer's `observe` method with our target element.

To clean up after ourselves, we return a function from the `useEffect` hook that calls the observer's `unobserve` method on our target element. This ensures that when our component unmounts or the element is removed from the page, we stop observing it.

Here's an example of how to use the `useEffect` hook to set up an `IntersectionObserver`.

```tsx
React.useEffect(() => {
    const ele = elementRef.current;
    if (!ele) {
        return;
    }

    const observer = new IntersectionObserver(([entry]) => {
        setIsVisible(entry.isIntersecting);
    });
    observer.observe(ele);

    return () => {
        observer.unobserve(ele);
    };
}, []);
```

Lastly, we create a `div` element that shows whether or not the observed element is partially visible.

```tsx
// Render
return (
    <div>
        {isVisible ? 'Element is partially visible' : 'Element is not partially visible'}
    </div>
);
```

Check out the demo below. Scroll up and down to see how the message updates automatically, letting you know if the target element is visible or not.

{% codesandbox jf53ty %}

## Checking if an element is fully visible

To ensure that an element is fully visible, we can update our implementation using the `intersectionRatio` property of the `entry` object passed to the IntersectionObserver's callback function.

The `intersectionRatio` property is a value between 0 and 1 that represents the percentage of the targeted element that is currently visible within the viewport. If this value is equal to 1, then the entire element is visible.

To achieve this, we can modify our existing code by checking whether `entry.intersectionRatio` is equal to 1 or not. If it's equal to 1, then we can set our state variable `isVisible` to `true`, indicating that the element is fully visible. Otherwise, we set it to `false`.

Here's an example implementation:

```tsx
React.useEffect(() => {
    const ele = elementRef.current;
    if (!ele) {
        return;
    }

    const observer = new IntersectionObserver(([entry]) => {
        setIsVisible(entry.intersectionRatio === 1);
    });
    observer.observe(ele);

    return () => {
        observer.unobserve(ele);
    };
}, []);
```

Although it may seem promising and workable in theory, in practice, it falls short. Go ahead and try scrolling down in the playground below. You'll notice that the message doesn't update even if you scroll over the entire element.

{% codesandbox qlvywy %}

The reason why the approach to check if an element is fully visible doesn't work is because `intersectionRatio` only considers when the element is partially or not visible at all.

We can fix this issue by adding an extra option to the `IntersectionObserver` constructor. This option is called `threshold`, and it lets us specify at what percentage of visibility we want our callback function to be executed.

To make sure our callback function is called when the observed element is fully visible, we can set the `threshold` value to 1. This means that our callback function will only be executed when the entire observed element is visible in the viewport.

It's worth noting that the `threshold` option can also accept an array of numbers. Here's how we can modify our existing code to implement this solution:

```tsx
React.useEffect(() => {
    const ele = elementRef.current;
    if (!ele) {
        return;
    }

    const observer = new IntersectionObserver(([entry]) => {
        setIsVisible(entry.isIntersecting);
    }, {
        threshold: 1,
    });
    observer.observe(ele);

    return () => {
        observer.unobserve(ele);
    };
}, []);
```

With this modification, the `isVisible` state variable now accurately shows whether or not the observed element is fully visible. Give it a try by scrolling up and down in the playground below to see how this updated implementation works.

{% codesandbox 3hy6ml %}

## Conclusion

In this post, we learned how to use the IntersectionObserver API with React. By encapsulating all the intersection observer functionality within the `useEffect` hook, we were able to check if an element is partially or fully visible using different threshold values.

Using the IntersectionObserver API can significantly improve an application's performance by reducing the number of calculations required to determine visibility changes. In our next post, we'll take it a step further and learn how to turn the implementation into a reusable hook and component.

---

If you want more helpful content like this, feel free to follow me:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)