Skip to content

Instantly share code, notes, and snippets.

@Icohen007
Last active June 3, 2021 08:11
Show Gist options
  • Save Icohen007/bcffa02dc48bda1cbf26d2e04e2c1f68 to your computer and use it in GitHub Desktop.
Save Icohen007/bcffa02dc48bda1cbf26d2e04e2c1f68 to your computer and use it in GitHub Desktop.
import { useRef, useEffect, useState } from 'react';
function debounce(func, wait) {
let timeout;
return function fn(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const useIntersection = (scrollRef) => {
const [event, setEvent] = useState(null);
const scrollObserver = (node) => {
new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0) {
setEvent(entry);
}
});
}).observe(node);
};
useEffect(() => {
if (scrollRef.current) {
scrollObserver(scrollRef.current);
}
}, [scrollRef]);
return event;
};
const usePullToRefresh = (id, threshold = 100) => {
const [pullFromBottom, setPullFromBottom] = useState(false);
const [momentumY, setMomentumY] = useState(0);
useEffect(() => {
// Disable the event when the wheel momentum slows down
if (pullFromBottom && momentumY < 10) {
setPullFromBottom(false);
}
}, [pullFromBottom, momentumY]);
const triggerEvent = debounce(() => {
// Wait for the deceleration of the wheel momentum to enhance the scrolling UX.
// The setTimeout can be removed without having too much impact.
setTimeout(() => {
setPullFromBottom(true);
}, 100);
}, 500);
const onWheel = (event) => {
const appHeight = document.getElementById(id).offsetHeight;
const winHeight = window.innerHeight;
const maxScrollY = appHeight - winHeight;
const scrollY = window.pageYOffset;
const atBottom = scrollY >= maxScrollY;
setMomentumY(event.deltaY);
// Pull harder to trigger the event.
if (atBottom && event.deltaY > threshold) {
triggerEvent();
}
};
useEffect(() => {
window.addEventListener('wheel', onWheel);
return () => {
window.removeEventListener('wheel', onWheel);
};
}, []);
return pullFromBottom;
};
const initialElements = ['Hey', 'You', 'Whats', 'Up'];
const InfiniteScroll = ({
children, className, onTop, onBottom, appId = '__next',
}) => {
const scrollTopRef = useRef(null);
const scrollBottomRef = useRef(null);
const reachTop = useIntersection(scrollTopRef);
const reachBottom = useIntersection(scrollBottomRef);
const pullFromBottom = usePullToRefresh(appId, 100);
useEffect(() => {
if (!onTop) {
return;
}
if (reachTop) {
onTop(reachTop);
}
}, [reachTop]);
useEffect(() => {
if (!onBottom) {
return;
}
if (reachBottom || pullFromBottom) {
onBottom({ reachBottom, pullFromBottom });
}
}, [reachBottom, pullFromBottom]);
return (
<div className={className}>
<div ref={scrollTopRef} className="checkpoint top" />
{children}
<div ref={scrollBottomRef} className="checkpoint bottom" />
</div>
);
};
export const Example = () => {
const [elements, setElements] = useState([0, ...initialElements]);
const [count, setCount] = useState(1);
const handleBottom = ({ pullFromBottom }) => {
if (pullFromBottom) {
setElements((prevElements) => [
...prevElements,
count,
...initialElements,
]);
setCount((prevCount) => prevCount + 1);
}
};
return (
<div className="App">
<h1>Infinity Scroll Example</h1>
<h2>Start Scroll to see some magic happen!</h2>
<InfiniteScroll onBottom={handleBottom}>
<p>
Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey Hey
Hey Hey Hey Hey Hey
{' '}
</p>
{elements.map((elem, i) => (
<p key={i}>{elem}</p>
))}
</InfiniteScroll>
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment