({
next,
hasMore,
dataLength,
scrollThreshold = 0.8,
scrollableTarget,
inverse = false,
}: UseInfiniteScrollOptions)
| 95 | * ``` |
| 96 | */ |
| 97 | export function useInfiniteScroll({ |
| 98 | next, |
| 99 | hasMore, |
| 100 | dataLength, |
| 101 | scrollThreshold = 0.8, |
| 102 | scrollableTarget, |
| 103 | inverse = false, |
| 104 | }: UseInfiniteScrollOptions): UseInfiniteScrollResult { |
| 105 | const [isLoading, setIsLoading] = useState(false); |
| 106 | const sentinelRef = useRef<HTMLDivElement>(null); |
| 107 | const actionTriggeredRef = useRef(false); |
| 108 | |
| 109 | // Stable ref so the observer callback always calls the latest next() |
| 110 | // without triggering observer reconnection when an inline function is passed. |
| 111 | const nextRef = useRef(next); |
| 112 | nextRef.current = next; |
| 113 | |
| 114 | const getScrollableNode = useCallback((): HTMLElement | null => { |
| 115 | if (scrollableTarget instanceof HTMLElement) return scrollableTarget; |
| 116 | if (typeof scrollableTarget === 'string') { |
| 117 | return document.getElementById(scrollableTarget); |
| 118 | } |
| 119 | return null; |
| 120 | }, [scrollableTarget]); |
| 121 | |
| 122 | // Reset the load guard when new data arrives. |
| 123 | useEffect(() => { |
| 124 | actionTriggeredRef.current = false; |
| 125 | setIsLoading(false); |
| 126 | }, [dataLength]); |
| 127 | |
| 128 | // IntersectionObserver lifecycle. |
| 129 | useEffect(() => { |
| 130 | if (!hasMore) return; |
| 131 | if (typeof IntersectionObserver === 'undefined') return; |
| 132 | |
| 133 | const sentinel = sentinelRef.current; |
| 134 | if (!sentinel) return; |
| 135 | |
| 136 | const root: Element | null = getScrollableNode(); |
| 137 | |
| 138 | const observer = new IntersectionObserver( |
| 139 | ([entry]) => { |
| 140 | if (!entry.isIntersecting || actionTriggeredRef.current) return; |
| 141 | actionTriggeredRef.current = true; |
| 142 | setIsLoading(true); |
| 143 | nextRef.current(); |
| 144 | }, |
| 145 | { |
| 146 | root, |
| 147 | rootMargin: buildRootMargin(scrollThreshold, inverse), |
| 148 | threshold: 0, |
| 149 | } |
| 150 | ); |
| 151 | |
| 152 | observer.observe(sentinel); |
| 153 | return () => observer.disconnect(); |
| 154 | }, [hasMore, scrollThreshold, inverse, getScrollableNode]); |
searching dependent graphs…