useIntersectionObserver()
This React Hook detects visibility of a component on the viewport using the IntersectionObserver
API natively present in the browser.
It can be very useful to lazy-loading of images, implementing "infinite scrolling" or starting animations for example.
Your must pass the ref element (from useRef()
).
It takes optionally root
, rootMargin
and threshold
arguments from the native IntersectionObserver
API and freezeOnceVisible
to only catch the first appearance too.
It returns the full IntersectionObserver's entry
object.
Source:
I discovered this way of using IntersectionObserver
via this post medium while playing to build a lazy-loaded collection of images.
The Hook
1import { RefObject, useEffect, useState } from 'react'23interface Args extends IntersectionObserverInit {4 freezeOnceVisible?: boolean5}67function useIntersectionObserver(8 elementRef: RefObject<Element>,9 {10 threshold = 0,11 root = null,12 rootMargin = '0%',13 freezeOnceVisible = false,14 }: Args,15): IntersectionObserverEntry | undefined {16 const [entry, setEntry] = useState<IntersectionObserverEntry>()1718 const frozen = entry?.isIntersecting && freezeOnceVisible1920 const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {21 setEntry(entry)22 }2324 useEffect(() => {25 const node = elementRef?.current // DOM Ref26 const hasIOSupport = !!window.IntersectionObserver2728 if (!hasIOSupport || frozen || !node) return2930 const observerParams = { threshold, root, rootMargin }31 const observer = new IntersectionObserver(updateEntry, observerParams)3233 observer.observe(node)3435 return () => observer.disconnect()3637 // eslint-disable-next-line react-hooks/exhaustive-deps38 }, [elementRef?.current, JSON.stringify(threshold), root, rootMargin, frozen])3940 return entry41}4243export default useIntersectionObserver
Usage
1import { useRef } from 'react'23import { useIntersectionObserver } from 'usehooks-ts'45const Section = (props: { title: string }) => {6 const ref = useRef<HTMLDivElement | null>(null)7 const entry = useIntersectionObserver(ref, {})8 const isVisible = !!entry?.isIntersecting910 console.log(`Render Section ${props.title}`, { isVisible })1112 return (13 <div14 ref={ref}15 style={{16 minHeight: '100vh',17 display: 'flex',18 border: '1px dashed #000',19 fontSize: '2rem',20 }}21 >22 <div style={{ margin: 'auto' }}>{props.title}</div>23 </div>24 )25}2627export default function Component() {28 return (29 <>30 {Array.from({ length: 5 }).map((_, index) => (31 <Section key={index + 1} title={`${index + 1}`} />32 ))}33 </>34 )35}
See a way to make this page better?
Edit there »