use-multi-intersection-observer
A React hook that provides a convenient way to observe multiple elements simultaneously using the Intersection Observer API.
INFO
Built on top of useIntersectionObserver hook for type-safety and consistent behaviors.
Features
- Multiple Observers: Create multiple intersection observers with a single hook call
- Unified API: Each observer follows the same pattern as
useIntersectionObserver - Shared configuration: Apply same options to all of the observers while maintaining individual keys
Problem It Solves
Multiple Hook Instance Boilerplate
Problem: Managing many intersection observers requires repetitive hook calls
// ❌ Without multi-observer hook - repetitive
const hero = useIntersectionObserver({ key: 'hero', threshold: 0.5 })
const about = useIntersectionObserver({ key: 'about', threshold: 0.5 })
const services = useIntersectionObserver({ key: 'services', threshold: 0.5 })
const contact = useIntersectionObserver({ key: 'contact', threshold: 0.5 })Solution: Single hook call for multiple observers
// ✅ With multi-observer hook
const sections = useMultiIntersectionObserver(['hero', 'about', 'services', 'contact'], { threshold: 0.5 })Type Safety at Scale
Problem: Maintaining type safety with multiple dynamically named properties
- Loses type inference while managing multiple observers manually
- No IntelliSense for dynamically generated property names
- Runtime errors due to typos in property access
Solution: This hook provides full type safety and comprehensive IntelliSense for all generated observer properties
// ✅ Full type safety and IntelliSense
const observers = useMultiIntersectionObserver(['hero', 'footer'] as const)
// TypeScript knows: observers.hero.setHeroElementRef, observers.hero.isHeroElementIntersecting
// TypeScript knows: observers.footer.setFooterElementRef, observers.footer.isFooterElementIntersectingParameters
| Parameter | Type | Required | Default Value | Description |
|---|---|---|---|---|
| keys | readonly Key[] | ✅ | - | Array of unique keys for creating named observers |
| options | MultipleObserverOptions | ❌ | undefined | Shared configuration for all observers |
Options Parameter
All options from useIntersectionObserver except key (which is provided via the keys array):
Type Definitions
Details
export type MultipleObserverOptions = Omit<IntersectionObserverOptions, 'key'>
// Return type is a record where each key maps to its observer result
type MultipleIntersectionObserverResult<Key extends string> = Record<
Key,
ReturnType<typeof useIntersectionObserver<Key>>
>Return Value(s)
The hook returns a record object where each key from the input array maps to its corresponding intersection observer result:
- Without key:
element,setElementRef,isElementIntersecting - With key:
{key}Element,set{Key}ElementRef,is{Key}ElementIntersecting
// Object contains all of the obervers
{
[key]: {
[`${key}Element`]: HTMLElement | null,
[`set${Capitalize<Key>}ElementRef`]: (element: HTMLElement | null) => void,
[`is${Capitalize<Key>}ElementIntersecting`]: boolean
}
} INFO
{key}Element: Holds the element reference which is being observed, it's initially undefined.
set{Capitalize<key>}ElementRef: Setter function to store the element reference within element, which is going tobe observed.
is{Capitalize<key>}ElementIntersecting: Holds the boolean intersection status of the element weather it is intersecting the screen or not.
Common Use Cases
- Multi-section navigation: Track visibility of multiple page sections for active navigation states
- Lazy loading galleries: Load multiple images or content blocks as they come into view
Usage Examples
Basic Multiple Observers
import { useMultiIntersectionObserver } from 'classic-react-hooks'
export default function MultipleObserversExample() {
const observers = useMultiIntersectionObserver(['header', 'main', 'footer'] as const, {
threshold: 0.3,
onIntersection: (entry) => {
console.log('Element intersection changed:', entry.target.id)
},
})
return (
<div>
<header
ref={observers.header.setHeaderElementRef}
className={`h-48 ${
observers.header.isHeaderElementIntersecting ? 'bg-blue-200' : 'bg-gray-500'
} flex items-center justify-center`}
>
<h1 className='text-2xl font-bold'>
Header {observers.header.isHeaderElementIntersecting ? '(Visible)' : '(Hidden)'}
</h1>
</header>
<main
ref={observers.main.setMainElementRef}
className={`h-screen ${
observers.main.isMainElementIntersecting ? 'bg-green-200' : 'bg-gray-200'
} flex items-center justify-center`}
>
<h2 className='text-xl font-semibold'>
Main Content {observers.main.isMainElementIntersecting ? '(Visible)' : '(Hidden)'}
</h2>
</main>
<footer
ref={observers.footer.setFooterElementRef}
className={`h-48 ${
observers.footer.isFooterElementIntersecting ? 'bg-red-200' : 'bg-gray-600'
} flex items-center justify-center`}
>
<h3 className='text-lg font-medium'>
Footer {observers.footer.isFooterElementIntersecting ? '(Visible)' : '(Hidden)'}
</h3>
</footer>
</div>
)
}Important
Each key in the array creates a separate useIntersectionObserver instance. While this provides maximum flexibility, consider the performance impact when observing many elements simultaneously.
One-Time Trigger
Example
import { useState } from 'react'
import { useMultiIntersectionObserver } from 'classic-react-hooks'
export default function OneTimeExample() {
const [hasBeenSeen, setHasBeenSeen] = useState(false)
const { setElementRef, isElementIntersecting } = useMultiIntersectionObserver({
onlyTriggerOnce: true,
threshold: 0.8,
onIntersection: (entry) => {
if (entry.isIntersecting) {
setHasBeenSeen(true)
console.log('Element seen for the first time!')
}
},
})
return (
<div className='h-[200vh]'>
<div className='mt-[150vh]'>
<div ref={setElementRef} className={`p-10 text-center ${hasBeenSeen ? 'bg-yellow-300' : 'bg-blue-200'}`}>
{hasBeenSeen ? 'I was seen!' : 'Scroll down to see me'}
</div>
</div>
</div>
)
}