import React, { useEffect, useReducer, memo, useRef } from "react"; import debounce from "lodash.debounce"; const VirtualizedList = ({ itemHeight, getItemsToRender, overscan, renderItem, totalItems, parentRef, array }) => { const containerRef = useRef(); const [state, dispatch] = useReducer( (prevState, nextState) => ({ ...prevState, ...nextState }), { amountToRender: 0, innerHeight: totalItems * itemHeight, topContainerHeight: 0, bottomContainerHeight: 0, itemsToRender: [], previousIndex: 0 } ); const onScroll = ({ target: { scrollTop } }) => { const { innerHeight } = state; const amountToRender = Math.round( parentRef.current.clientHeight / itemHeight + 2 * overscan ); const startIndex = Math.max( 0, Math.floor(scrollTop / itemHeight) - overscan ); if ( amountToRender === state.amountToRender && state.previousIndex === startIndex ) return; const itemsToRender = getItemsToRender(startIndex, amountToRender, array); const topContainerHeight = Math.max(startIndex * itemHeight, 0); const bottomContainerHeight = Math.max( innerHeight - topContainerHeight - amountToRender * itemHeight, 0 ); dispatch({ amountToRender, previousIndex: startIndex, itemsToRender, topContainerHeight, bottomContainerHeight, windowHeight: window.innerHeight }); }; useEffect(() => { onScroll({ target: { scrollTop: containerRef.current.scrollTop } }); }, []); useEffect(() => { const debouncedScroll = debounce( () => onScroll({ target: { scrollTop: containerRef.current.scrollTop } }), 200 ); const resizeListener = () => { if (state.windowHeight !== window.innerHeight) { debouncedScroll(); } }; window.addEventListener("resize", resizeListener); return () => window.removeEventListener("resize", resizeListener); }, [state.windowHeight]); useEffect(() => console.log("Virtual Rendered")); return (
{state.itemsToRender?.map(renderItem)}
); }; export default memo(VirtualizedList); // How to use // Function used by // It'll receive the array, index and quantity to render // It should return items to render const getItemsToRender = (index, amountToRender, data) => { const items = []; const startIndex = Math.max(0, index); const endIndex = Math.min(index + amountToRender, data.length); for (let i = startIndex; i < endIndex; i += 1) { items.push(data[i]); } return items; }; (
{item.Name}
)} parentRef={parentRef} />