import { useEffect, useRef, useState } from "react" import { useInView, IntersectionOptions } from "react-intersection-observer" import { Awaitable } from "../../types/utils" export type UseScrollingPaginationOptions = { hasNext?: boolean | undefined loadNext: () => Awaitable isLoadingNext: boolean onError?: () => void } & IntersectionOptions export const useScrollingPagination = ({ hasNext, loadNext, isLoadingNext, onError, // Provide a default rootMargin to trigger the observer ~200px before the element is in actual view. rootMargin = "200px 0px", ...rest }: UseScrollingPaginationOptions) => { const [isLoading, setIsLoading] = useState(isLoadingNext) const idleCallbackRef = useRef(undefined) const { ref, inView } = useInView({ skip: !hasNext, root: typeof window !== "undefined" ? window.document : null, threshold: 0, rootMargin, ...rest, }) useEffect(() => { if (inView && !isLoading && hasNext) { setIsLoading(true) Promise.resolve(loadNext()) .then(() => { // Give browser time to render the things that were just fetched, so we dont re-issue the same pagination request twice idleCallbackRef.current = requestIdleCallback(() => { idleCallbackRef.current = undefined setIsLoading(false) }) }) .catch(() => { setIsLoading(false) onError?.() }) } }, [inView, loadNext, isLoading, hasNext, onError]) useEffect(() => { return () => { if (idleCallbackRef.current) { cancelIdleCallback(idleCallbackRef.current) } } }, []) return { ref, isLoading } }