| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; |
| import { throttle } from 'lodash'; |
|
|
| interface UseInfiniteScrollOptions { |
| hasNextPage?: boolean; |
| isLoading?: boolean; |
| fetchNextPage: () => void; |
| threshold?: number; |
| throttleMs?: number; |
| } |
|
|
| |
| |
| |
| |
| export const useInfiniteScroll = ({ |
| hasNextPage = false, |
| isLoading = false, |
| fetchNextPage, |
| threshold = 0.8, // Trigger when 80% scrolled |
| throttleMs = 200, |
| }: UseInfiniteScrollOptions) => { |
| |
| const resizeObserverRef = useRef<ResizeObserver | null>(null); |
| const [scrollElement, setScrollElementState] = useState<HTMLElement | null>(null); |
|
|
| |
| const handleNeedToFetch = useCallback(() => { |
| if (!scrollElement) return; |
|
|
| const { scrollTop, scrollHeight, clientHeight } = scrollElement; |
|
|
| |
| const scrollPosition = (scrollTop + clientHeight) / scrollHeight; |
|
|
| |
| const shouldFetch = scrollPosition >= threshold && hasNextPage && !isLoading; |
|
|
| if (shouldFetch) { |
| fetchNextPage(); |
| } |
| }, [scrollElement, hasNextPage, isLoading, fetchNextPage, threshold]); |
|
|
| |
| const throttledHandleNeedToFetch = useMemo( |
| () => throttle(handleNeedToFetch, throttleMs), |
| [handleNeedToFetch, throttleMs], |
| ); |
|
|
| |
| useEffect(() => { |
| return () => { |
| throttledHandleNeedToFetch.cancel?.(); |
| }; |
| }, [throttledHandleNeedToFetch]); |
|
|
| |
| useEffect(() => { |
| if (isLoading === false && scrollElement) { |
| |
| const rafId = requestAnimationFrame(() => { |
| throttledHandleNeedToFetch(); |
| }); |
| return () => cancelAnimationFrame(rafId); |
| } |
| }, [isLoading, scrollElement, throttledHandleNeedToFetch]); |
|
|
| |
| useEffect(() => { |
| const element = scrollElement; |
| if (!element) return; |
|
|
| |
| element.addEventListener('scroll', throttledHandleNeedToFetch, { passive: true }); |
|
|
| |
| if (resizeObserverRef.current) { |
| resizeObserverRef.current.disconnect(); |
| } |
|
|
| resizeObserverRef.current = new ResizeObserver(() => { |
| |
| throttledHandleNeedToFetch(); |
| }); |
|
|
| resizeObserverRef.current.observe(element); |
|
|
| |
| throttledHandleNeedToFetch(); |
|
|
| return () => { |
| element.removeEventListener('scroll', throttledHandleNeedToFetch); |
| |
| if (resizeObserverRef.current) { |
| resizeObserverRef.current.disconnect(); |
| resizeObserverRef.current = null; |
| } |
| }; |
| }, [scrollElement, throttledHandleNeedToFetch]); |
|
|
| |
| const setScrollElement = useCallback((element: HTMLElement | null) => { |
| setScrollElementState(element); |
| }, []); |
|
|
| return { |
| setScrollElement, |
| }; |
| }; |
|
|