Skip to content

Instantly share code, notes, and snippets.

@ezekielchentnik
Created August 26, 2019 19:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ezekielchentnik/c14293b84ca87acd22f2d62d0b8fa030 to your computer and use it in GitHub Desktop.
Save ezekielchentnik/c14293b84ca87acd22f2d62d0b8fa030 to your computer and use it in GitHub Desktop.
Virtualized (Windowed) List w/ React hooks
/**
* VirtualList: a universal windowed list for large datasets
*
* @param {Array} data - array of rows to render
* @param {string} rowHeight - height of row container in pixels
* @param {function} renderRow - function to render a single homogenous row
* @param {number} overscanCount - number of extra rows to pre-render ahead/behind the window
* @param {Object} rest - additional props
*
* @example
* const Row = row => <div>{row[0]}</div>
* const data = [
* ["Name 1", "Age 1"],
* ["Name 2", "Age 2"]
* ];
* <VirtualList data={data} renderRow={Row} />
*
*/
export const VirtualList = ({ data, rowHeight, renderRow, overscanCount = 10 }) => {
// grab ref to update offset, could also useCallback and getBoundingClientRect
const ref = useRef();
const [offset, setOffset] = useState(0);
const height = ref.current ? ref.current.offsetHeight : 0;
let total = data.length;
let _start = (offset / rowHeight) | 0;
let visibleRowCount = (height / rowHeight) | 0;
let _end = _start + visibleRowCount;
// later we may want to expose paging numbers (e.g. 1-10 of 200)
// let start = _start + 1;
// let end = _end + 1 === total ? total : _end;
// drastically improve performance by overscanning and rendering extra rows above or below the windowed list
if (overscanCount) {
_start = Math.max(0, _start - overscanCount);
_end = _end + overscanCount;
}
// grab our current window
let selection = data.slice(_start, _end);
return (
<div
className="vl-list"
ref={ref}
onScroll={e => {
// update offset to adjust our moving/windowed pane
// sometimes the browser does not call this if you momentum scroll really really fast w/ rubber-banding, needs investigation
setOffset(e.currentTarget.scrollTop);
}}
>
<div className="vl-scroller" style={{ height: `${total * rowHeight}px` }}>
<div className="vl-item" style={{ top: `${_start * rowHeight}px` }}>
{selection.length ? (
selection.map(renderRow)
) : (
<div className="vl-info">No data found.</div>
)}
</div>
</div>
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment