Skip to content

Instantly share code, notes, and snippets.

@saltnpixels
Created May 3, 2022 18:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saltnpixels/f4e4ab895846033409d396df4bd8282a to your computer and use it in GitHub Desktop.
Save saltnpixels/f4e4ab895846033409d396df4bd8282a to your computer and use it in GitHub Desktop.
React Infinite Scroller that works smartly
// must have a loading state that turns true when fetching data and false afterwards
// callback is responsible for setting loading state as well as deciding if it should even run. (check if pages left etc...)
// scrollingElement defaults to viewport if none used. If wanted put it as a ref on a scrollable element
// sentry must be placed as a ref and is not optional
import { useCallback, useEffect, useRef, useState } from 'react';
export default function useInfiniteScroller({
isLoading,
callback,
}: {
isLoading: boolean;
callback: () => void;
}) {
const scrollingElement = useRef<HTMLElement | null>(null);
const observer = useRef<IntersectionObserver | null>(null);
const [sentryReady, setSentryReady] = useState<any>(null);
// the magic that makes the callback refresh with non-stale state
const cbRef = useRef(() => {});
cbRef.current = callback;
const onIntersection = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (isLoading) {
return;
}
if (entry.isIntersecting) {
cbRef.current();
}
});
};
// runs after sentry is pointing to a ref. observes element
const sentry = useCallback((sentryElement) => {
if (sentryElement) {
setSentryReady(sentryElement);
}
}, []);
useEffect(() => {
if (sentryReady && observer) {
observer.current = new IntersectionObserver(onIntersection, {
root: scrollingElement.current || null, // useViewport ? null : element, // default is the viewport
threshold: 1, // percentage of target's visible area. Triggers "onIntersection"
});
observer.current.observe(sentryReady as Element);
}
return () => {
observer.current?.disconnect();
};
}, [sentryReady]);
return [sentry, scrollingElement];
}
@saltnpixels
Copy link
Author

the loading is to stop it from running everytime there is an intersection. if thats not needed just set it to true all the time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment