All questions

useVirtualList

Premium

useVirtualList

Rendering 100,000 rows into the DOM will freeze the browser — but the user only ever sees a dozen at a time. Virtualization (windowing) renders only the rows currently in the viewport (plus a small buffer), positioned so the scrollbar still behaves as if all rows existed. useVirtualList does the math: given the item count, row height, and viewport height, it tells you which rows to render and where to place each one.

Implement useVirtualList({ itemCount, itemHeight, containerHeight, overscan = 3 }). Return virtualItems (the visible rows with their absolute offsets), totalHeight (so the scroll area is the right size), startIndex/endIndex, and an onScroll handler that tracks the container's scrollTop.

Signature

function useVirtualList({ itemCount, itemHeight, containerHeight, overscan = 3 }) {
  // returns { virtualItems, totalHeight, startIndex, endIndex, scrollTop, onScroll }
}

Examples

const { virtualItems, totalHeight, onScroll } =
  useVirtualList({ itemCount: rows.length, itemHeight: 32, containerHeight: 400 });

<div style={{ height: 400, overflow: 'auto' }} onScroll={onScroll}>
  <div style={{ height: totalHeight, position: 'relative' }}>
    {virtualItems.map(({ index, offsetTop }) => (
      <Row key={index} style={{ position: 'absolute', top: offsetTop }} {...rows[index]} />
    ))}
  </div>
</div>

Notes

  • Total heightitemCount * itemHeight, so the scroll thumb reflects the full list even though most rows aren't rendered.
  • Visible range — from scrollTop and containerHeight: start = floor(scrollTop / itemHeight), end = floor((scrollTop + containerHeight) / itemHeight).
  • Overscan — render a few extra rows above/below the viewport so fast scrolling doesn't flash blank; clamp start >= 0 and end <= itemCount - 1.
  • Absolute offsets — each visible item's offsetTop = index * itemHeight positions it correctly inside the full-height spacer.
WE’RE LISTENING

Help make UIReady better

Found a bug or have an idea? Tell us about it.

Up to 3 files · 5 MiB each · JPG, PNG, WebP, MP4, WebM, MP3, M4A or WAV