@supunlakmal/hooks
Version:
A collection of reusable React hooks
61 lines • 2.76 kB
JavaScript
import { useState, useEffect, useRef, useCallback } from 'react';
/**
* Optimizes rendering of long lists by only rendering the items currently visible
* in the viewport, plus a configurable number of items above and below (overscan).
* Assumes fixed item height for simplicity.
*
* @template T The type of the items in the list.
* @param options Configuration options including the list data, item height, and container ref.
* @returns An object containing the items to render and the total list height.
*/
export const useVirtualList = (options) => {
var _a;
const { list, itemHeight, containerRef, overscan = 5, initialScrollTop = 0, onScroll, } = options;
const [scrollTop, setScrollTop] = useState(initialScrollTop);
const innerRef = useRef(null);
// Update scroll position on scroll events
const handleScroll = useCallback((event) => {
const currentScrollTop = event.target.scrollTop;
setScrollTop(currentScrollTop);
onScroll === null || onScroll === void 0 ? void 0 : onScroll(currentScrollTop);
}, [onScroll]);
useEffect(() => {
const container = containerRef.current;
if (!container)
return;
// Set initial scroll position if provided
if (initialScrollTop > 0) {
container.scrollTop = initialScrollTop;
}
container.addEventListener('scroll', handleScroll);
return () => container.removeEventListener('scroll', handleScroll);
}, [containerRef, handleScroll, initialScrollTop]);
const totalHeight = list.length * itemHeight;
// Calculate visible items based on scroll position
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const endIndex = Math.min(list.length - 1, Math.floor((scrollTop + (((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight) || 0)) / itemHeight) + overscan);
const virtualItems = list
.slice(startIndex, endIndex + 1)
.map((item, index) => {
const originalIndex = startIndex + index;
return {
data: item,
index: originalIndex,
offsetTop: originalIndex * itemHeight,
};
});
// Style the inner container to position items correctly
useEffect(() => {
if (innerRef.current) {
// Although items are positioned absolutely, setting the height ensures scrollbar size is correct.
innerRef.current.style.height = `${totalHeight}px`;
innerRef.current.style.position = 'relative'; // Needed for absolute positioning of children
}
}, [totalHeight]);
return {
virtualItems,
totalHeight,
innerRef,
};
};
//# sourceMappingURL=useVirtualList.js.map