useInfiniteScroll

Automatically trigger a callback when an element scrolls into view.

Endless Gallery

Scroll down to load more colors

0 Items Loaded

Usage

import { useState } from 'react'
import { useInfiniteScroll } from 'usehooks-ts'
 
export default function App() {
  const [items, setItems] = useState([1, 2, 3])
  const [loading, setLoading] = useState(false)
 
  const loadMore = () => {
    if (loading) return
    setLoading(true)
    setTimeout(() => {
      setItems(prev => [...prev, prev.length + 1])
      setLoading(false)
    }, 1000)
  }
 
  const [loaderRef] = useInfiniteScroll(loadMore)
 
  return (
    <div>
      {items.map(i => <div key={i}>{i}</div>)}
      <div ref={loaderRef}>{loading ? 'Loading...' : 'Scroll for more'}</div>
    </div>
  )
}

API

const [loaderRef, isVisible] = useInfiniteScroll(callback, options?)

Parameters

NameTypeDefaultDescription
callback() => void-Function to call when visible
optionsUseInfiniteScrollOptions{ threshold: 0.5 }IntersectionObserver options

Return Values

NameTypeDescription
loaderRefRefObjectRef to attach to the target element
isVisiblebooleanWhether the element is currently visible

Hook

import { useEffect, useRef, useState, useCallback } from 'react'
 
export interface UseInfiniteScrollOptions {
  threshold?: number | number[]
  root?: Element | Document | null
  rootMargin?: string
}
 
export function useInfiniteScroll(
  callback: () => void,
  options: UseInfiniteScrollOptions = {},
) {
  const [isIntersecting, setIntersecting] = useState(false)
  const observerRef = useRef<IntersectionObserver | null>(null)
  const elementRef = useRef<HTMLElement | null>(null)
 
  const handleIntersect = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const entry = entries[0]
      setIntersecting(entry.isIntersecting)
      if (entry.isIntersecting) {
        callback()
      }
    },
    [callback],
  )
 
  useEffect(() => {
    const { threshold = 0.5, root = null, rootMargin = '0px' } = options
 
    observerRef.current = new IntersectionObserver(handleIntersect, {
      threshold,
      root,
      rootMargin,
    })
 
    const currentElement = elementRef.current
    if (currentElement) {
      observerRef.current.observe(currentElement)
    }
 
    return () => {
      if (observerRef.current) {
        observerRef.current.disconnect()
      }
    }
  }, [handleIntersect, options])
 
  return [elementRef, isIntersecting] as const
}