Virtualized Lists
Render only the visible rows (plus a small overscan) so long lists stay performant without thousands of DOM nodes.
The problem I keep seeing
Feeds, tables, and dropdowns sometimes need to display thousands of items. Rendering every item creates thousands of DOM nodes, which slows layout and paint and makes scrolling janky. You need to render only what’s in (or near) the viewport and reuse nodes as the user scrolls.
Naive approach
Map over the full array and render one node per item. Simple and fine for a few hundred items; for 10k+ it becomes a bottleneck.
First improvement
“Window” the list: from the scroll position and container height, compute the range of indices that are visible (plus a small overscan). Render only those items and use a spacer (height = startIndex * itemHeight) and transform: translateY so the scrollable height and scroll position stay correct.
Remaining issues
- Variable height: If items have different heights, you need to measure (or estimate) and compute offsets; a library handles this with a size cache.
- Scroll restoration: When navigating back to the list, restoring scroll position requires storing and reapplying the scroll offset (or the first visible index).
- Accessibility: Screen readers expect to know list length and may virtualize differently; use
aria-setsize/aria-posinsetand consider “load more” or a non-virtualized fallback for assistive tech if needed.
Production pattern
Use @tanstack/react-virtual or react-window. They handle visible range, overscan, total size, and (with the right options) variable height. Prefer a stable estimateSize for fixed rows; use dynamic measurement when heights vary. Keep list semantics (role="list", role="listitem") and add aria-setsize / aria-posinset if you care about screen reader list navigation.
Variable height
estimateSize and let the library measure (e.g. measureElement in TanStack Virtual). The snippet above shows the idea; see library docs for the exact API.When I use this
- Long lists (500+ items): Feeds, tables, select dropdowns with many options. Virtualization keeps DOM small and scroll smooth.
- Skip when: List has < 100–200 items; the overhead of virtualization isn’t worth it and full render is fine.
Gotchas
- Stable keys: Use a unique, stable id per item so when the window shifts, React doesn’t remount unnecessarily.
- Overscan: Render a few extra items above and below the viewport so fast scrolling doesn’t show blank gaps; 5–10 is usually enough.
- Infinite scroll + virtual: You can combine with
useInfiniteQuery: virtualize the concatenated pages and grow the list as the user scrolls near the end.