Detecting the Visibility of a DOM Element in React
Tags: react, javascript, webdev, tutorial
Published: 2022-11-05 18:14:38

It is often important to track the user's activity on a website for analytical reasons. Thanks to JavaScript's Intersection Observer API, detecting the visibility of an element in a website has now become easier.

> The Intersection Observer API observes an element and detects when it is visible in the viewport, and in turn, triggers a callback function.

In this article, we will walk through an example of how we can create a **useIntersection Custom Hook** in React. This hook detects when a particular section of a page is viewed by the user for more than 3 seconds. 

Check out the **demo** below.

{% codesandbox 7k0eim %}

## Basic Usage

We will first create an `isVisible` state inside the `useIntersection` hook and set its initial value to **false**.

**useIntersection.js**
```javascript
const useIntersection = () => {
	const [isVisible, setIsVisible] = useState(false);

	return isVisible;
}
```

To use the Intersection Observer API, we need to create an observer in the `useEffect` hook.

**useIntersection.js**
```javascript
import { useState, useEffect } from "react";

const useIntersection = (element, rootMargin = "0px") => {
	const [isVisible, setIsVisible] = useState(false);

	useEffect(() => {
		let currentElement;
		
		const observer = new IntersectionObserver(
		([{ isIntersecting }]) => {}, { rootMargin });
 
		if (element?.current) {
			currentElement = element.current;
			observer.observe(currentElement);
		}
		
		return () => {
			observer.unobserve(currentElement);
		};
	}, [element, rootMargin]);

	return isVisible;
}

export default useIntersection;
```

The Intersection API takes two parameters:
 - The callback function
 - Observer's options
 
The observer watches the target element and the callback function has an `isIntersecting` boolean value. The `rootMargin` is one of the options of the Intersection API. The `rootMargin` parameter is the minimum number of pixels which when visible in the viewport would change the `isIntersecting` value. 

In this scenario, since the `rootMargin` is `0px`, the `isIntersecting` value changes to **true** as soon as the `currentElement` is visible in the viewport.

To capture the `isIntersecting` state after 3 seconds, we need to create a timer and set the `isVisible` state to the `isIntersecting` value as shown below.

**useIntersection.js**
```javascript
const useIntersection = (element, delay = 3000, rootMargin = "0px") => {
	const [isVisible, setIsVisible] = useState(false);

	useEffect(()  =>  {
	    let currentElement, timer;

	    const observer = new IntersectionObserver(
          [{ isIntersecting }])  =>  {
		    if (timer && !isIntersecting) clearTimeout(timer);

		    timer = setTimeout(()  =>  { 
                        setIsVisible(isIntersecting); 
                    }, delay);
	    },
	    { rootMargin });

	    if (element?.current) {
		    currentElement = element.current;
		    observer.observe(currentElement);
	    }

	    return () => {
		    clearTimeout(timer);
		    observer.unobserve(currentElement);
	    };
       }, [element, rootMargin]);

	return isVisible;
}

export default useIntersection;
```

## Calling the useIntersection Hook

The `useIntersection` hook has been used in App.js as shown below.

**App.js**
```jsx
import { useEffect,  useRef } from "react";
import useIntersection from "./customHook/useIntersection";

export default function App() {
	const sectionFourRef = useRef(null);
	const sectionIsInViewport  = useIntersection(sectionFourRef);

	useEffect(() => {
		if (sectionIsInViewport && sectionFourRef?.current)  {
			const sectionText = sectionFourRef.current.textContent;
			window.alert(`${sectionText} is visible in the viewport!`);
		}
	},  [sectionIsInViewport]);
	
return  (
	<>
	<header>Header</header>
	<section>Section-1</section>
	<section>Section-2</section>
	<section>Section-3</section>
	<section ref={sectionFourRef}>Section-4</section>
	<section>Section-5</section>
	<footer>Footer</footer>
	</>
);}
```

In the above code, we created a ref called `sectionFourRef` and passed it to the `useIntersection` custom hook. The hook watches for the **Section-4** element in the page and displays an alert box every time the user views the section for more than 3 seconds.