Last active
August 6, 2020 09:03
-
-
Save sandeshdamkondwar/20509bd89687f0b38605b4c3de1c071c to your computer and use it in GitHub Desktop.
Infinite Scroller React Hook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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