Skip to content

Instantly share code, notes, and snippets.

@sandeshdamkondwar
Last active August 6, 2020 09:03
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 sandeshdamkondwar/20509bd89687f0b38605b4c3de1c071c to your computer and use it in GitHub Desktop.
Save sandeshdamkondwar/20509bd89687f0b38605b4c3de1c071c to your computer and use it in GitHub Desktop.
Infinite Scroller React Hook
import React, { useEffect, useState, useRef } from "react";
// Helpers
import { debounce, generateCols, getNoOfCols } from "../../helpers/";
// Hooks
import { useInifiniteScroller } from "../../hooks/useInfiniteScroller";
// Defs
import { IGifItem } from "../../defs/interfaces";
// Components
import GIFPlayer from "../../components/GIFPlayer";
import loader from "../../images/loader.svg";
import CONFIG from "../../project.config";
const getImagesLimit = (windowWidth: number) => {
return windowWidth < 500
? CONFIG.IMAGE_LOADING_LIMIT
: CONFIG.IMAGE_LOADING_LIMIT_DESKTOP;
};
function GIFContainer({
fetchGifs,
}: {
fetchGifs: (offset: number, limit: number) => Promise<any>;
}) {
const [cols, setCols] = useState<any[]>([]);
const [offset, setOffset] = useState<number>(0);
const [colHeights, setColHeights] = useState<number[]>([]);
const { fetch, setFetching } = useInifiniteScroller({
scollThreshold: CONFIG.SCROLL_LISTENER_THRESHOLD,
bottomThreshold: CONFIG.LOADER_BOTTOM_THRESHOLD,
});
const delayedLoading = useRef(
debounce(
() => {
const offsetLimit = getImagesLimit(window.innerWidth);
// Updating offset will take care of fetching data with new offset
setOffset((offset) => {
return offset + offsetLimit + 1;
});
},
CONFIG.LOADER_DEBOUNCE_TIME_IN_MS,
false
)
).current;
useEffect(() => {
const windowWidth = window.innerWidth;
const offsetLimit = getImagesLimit(windowWidth);
fetchGifs(offset, offsetLimit).then((res: any) => {
let { items, heights } = generateCols({
data: res.data instanceof Array ? res.data : [res.data],
screenWidth: windowWidth,
cols: getNoOfCols(windowWidth),
});
// Merge heights
const mergedHeights =
colHeights.length === 0
? colHeights.map((colHeight, idx) => colHeight + heights[idx])
: heights;
setColHeights(mergedHeights);
// Merge with old result
items = items || [];
const mergedCols = cols.length
? cols.map((col, idx) => [...cols[idx], ...items[idx]])
: items;
setCols(mergedCols);
setFetching(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fetchGifs, offset]);
if (fetch && cols.length > 0) {
delayedLoading();
}
const imageContainerHeight = Math.max(...colHeights);
return (
<div className="gif-items-container">
<div
className="gif-items-cols"
style={{ height: `${imageContainerHeight}px` }}
>
{cols.map((colItems: Array<any>, key: number) => {
return (
<div key={key} className="gif-items-col">
{colItems.map((colItem: IGifItem, itemKey: number) => (
<GIFPlayer
className="gif-item"
key={itemKey}
gif={colItem.gif.url}
still={colItem.still.url}
title={colItem.title}
height={colItem.height}
/>
))}
</div>
);
})}
</div>
{fetch && <img src={loader} className="loader-image" alt="loader" />}
</div>
);
}
export default React.memo(GIFContainer);
// Note: Uncomment commented code if you want to shift to ts mode
import { useState, useEffect } from "react";
// interface IProps {
// scollThreshold?: number;
// bottomThreshold: number;
// }
export const useInifiniteScroller = ({
scollThreshold, // threshold for scroll listerner
bottomThreshold, // set the fetching true once arrived at the bottom
// }: IProps) => {
}) => {
const [fetching, setFetching] = useState(false);
const [reqAnimationFrameId, setReqAnimationFrameId] = useState(0);
useEffect(() => {
const threshold = scollThreshold || 1;
let { pageYOffset, innerHeight } = window;
let ticking = false;
const updateScrollPosition = () => {
const scrollY = window.pageYOffset;
const totalHeight = document.body.clientHeight;
if (Math.abs(scrollY - pageYOffset) < threshold) {
// We haven't exceeded the threshold
ticking = false;
return;
}
ticking = false;
if (!fetching) {
const clientBottomPosition = scrollY + innerHeight;
const reachedDown =
totalHeight - clientBottomPosition < bottomThreshold;
if (reachedDown) {
setFetching(true);
} else {
setFetching(false);
}
}
};
const onScroll = () => {
if (!ticking) {
setReqAnimationFrameId(
window.requestAnimationFrame(updateScrollPosition)
);
ticking = true;
}
};
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
window.cancelAnimationFrame(reqAnimationFrameId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return {
fetch: fetching,
setFetching,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment