In our previous post, we learned how to use the useEffect
hook to integrate IntersectionObserver with React. But what if we want to use this implementation in other places? It's always a good idea to turn it into a reusable function that we can use in different components and projects.
Creating a reusable hook can save us a lot of time and effort. Instead of writing the same code multiple times, we can abstract it into a single function that can be used across different projects. This not only saves us time but also makes our code more organized and easier to maintain.
Moreover, creating a reusable hook allows us to share our code with the community. We can publish our hooks as NPM packages, which other developers can use in their own projects. This promotes code reuse and helps to build a stronger React ecosystem.
In this post, we'll learn how to create a custom hook that encapsulates the IntersectionObserver API logic. Let's dive in!
Simplifying the IntersectionObserver API with a custom hook
In the previous post, we introduced an implementation that uses a React ref to represent an element and a Boolean state to indicate whether the element is partially visible or not.
To make this implementation reusable, we created a useIntersectionObserver
hook that encapsulates the IntersectionObserver logic. This custom hook returns an array containing two elements: ref
and isVisible
.
The ref
element is a callback function that receives the DOM node as its argument. We attach this ref to the component's root element, which we want to observe for intersection. Once the ref is attached to the target element via the ref
attribute, the callback will be executed to update our internal node
state. Initially, the node
state is set to null
.
The second element returned by this hook is a Boolean value indicating if the observed element is currently intersecting with the viewport.
Here's how we can draft the hook implementation.
export const useIntersectionObserver = () => {
const [node, setNode] = React.useState<HTMLElement>(null);
const [isVisible, setIsVisible] = React.useState(false);
const ref = React.useCallback((nodeEle) => {
setNode(nodeEle);
}, []);
return [ref, isVisible];
};
To track changes in a node's intersection, we can use the useEffect()
hook. This hook has two arguments: a callback function and an array of dependencies. In our case, we only need to run the effect when the node
element exists, so we pass [node]
as the second argument.
Inside the callback function, we first check if the node exists. If it does, we create a new instance of IntersectionObserver
and pass it a callback that gets called every time there is an intersection change. The callback receives an array of IntersectionObserverEntry
objects, but since we are only observing one element, we can safely assume that this array will always have only one element.
We then update our internal state with the current value of entry.isIntersecting
, which tells us whether or not the observed element is currently intersecting with the viewport.
Finally, we return a cleanup function that stops observing the node when it's no longer needed. This ensures that we don't waste resources by observing elements that are no longer in use.
Here's the updated version of the hook for your reference:
export const useIntersectionObserver = () => {
React.useEffect(() => {
if (!node) {
return;
}
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
});
observer.observe(node);
return () => {
observer.unobserve(node);
};
}, [node]);
return [ref, isVisible];
};
Using the custom hook
To use the useIntersectionObserver
hook, we start by importing it into our component. Once imported, we simply call the hook and destructure its return values into variables.
import { useIntersectionObserver } from './useIntersectionObserver';
const [elementRef, isVisible] = useIntersectionObserver();
The first variable is a ref
callback function that we pass to the element we want to observe for intersection. This is done using the ref
attribute in JSX.
// Render
<div className="element" ref={elementRef}>
...
</div>
The second variable is a simple true
or false
value that tells us if the element is currently visible on the screen or not. We can use this value to show or hide content or take other actions based on whether or not an element is visible to the user.
// Render
<div className="result">
{isVisible ? 'Element is partially visible' : 'Element is not partially visible'}
</div>
Take a look at the demo below. Simply scroll up and down to see the message update automatically, informing you whether or not the target element is partially visible.
Customizing the hook further
Now that our hook can detect whether an element is partially visible in the viewport, what if we want to know if it's fully visible, like we did before? In our previous post, we achieved this by setting the threshold
value to an array of 1.
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
}, {
threshold: [1],
});
To support the threshold value, let's modify our useIntersectionObserver
hook. We can add a new parameter called threshold
that takes an array of numbers between 0 and 1. This array specifies the percentage of the target's visibility at which the observer's callback should be executed.
Next, we pass this threshold value as an option to the IntersectionObserver
constructor. In the updated implementation, we pass a second argument to IntersectionObserver
with an options object containing our threshold value.
export const useIntersectionObserver = ({
threshold,
}: {
threshold: number[],
}) => {
React.useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
}, {
threshold,
});
// ...
}, [node]);
};
Now, when we call our custom hook, we can pass in a threshold value as an argument. For instance, if we want to find out if an element is completely visible on the screen, we can set a threshold value of [1]
. This will help us ensure that our element is fully visible before taking any action.
const [elementRef, isVisible] = useIntersectionObserver({
threshold: [1],
});
Our custom hook just got even more flexible and reusable! We added support for the threshold
parameter, making it suitable for various intersection detection scenarios.
Give it a go by scrolling up and down in the playground below to see how this updated implementation works.
Conclusion
In conclusion, we have learned how to make a reusable hook for IntersectionObserver in React. By putting the IntersectionObserver logic in a custom hook, we can easily use it across different components and projects. We also learned how to make this hook even more customizable by adding support for the threshold
parameter.
Creating custom hooks is just one of the many ways we can make our code more modular and easier to maintain. It allows us to take complex logic and turn it into reusable building blocks that can be shared with other developers.
See also
If you want more helpful content like this, feel free to follow me:
Top comments (0)