Created
August 26, 2019 19:00
-
-
Save ezekielchentnik/c14293b84ca87acd22f2d62d0b8fa030 to your computer and use it in GitHub Desktop.
Virtualized (Windowed) List w/ React hooks
This file contains 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
/** | |
* 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