React-List: Virtualize Large Lists & Boost Scroll Performance







React-List: The Complete Guide to Virtualizing Large Lists in React

Updated · 15 min read · Tags:
react-list, React list virtualization,
React performance optimization, React scroll performance

There is a moment every React developer dreads: the product manager walks in, casually mentions that
the data table now needs to display 50,000 rows, and walks out smiling. Your component tree, your
browser’s memory, and your career ambitions all flash before your eyes simultaneously. Fortunately,
react-list
exists precisely for this moment. It is a versatile, lightweight
React list component that implements list virtualization — the
technique of rendering only what the user can actually see, rather than dumping 50,000 DOM nodes
onto the page and hoping for the best.

This guide covers everything from react-list installation and initial
react-list setup through to advanced patterns like variable height
rendering
, React infinite scroll, and performance tuning for genuinely
large datasets. Whether you are getting started for the first time or hunting for
advanced react-list configuration options, you will find actionable answers here —
with working code examples throughout.

Why React List Virtualization Matters (and Why Most Devs Skip It Too Long)

The browser DOM is a magnificent piece of engineering, right up until the moment you ask it to
manage 10,000 list items simultaneously. Each DOM node carries memory overhead, triggers layout
recalculations, and demands paint cycles. Render a few hundred items and you will probably notice
nothing. Render a few thousand and your scroll frame rate will begin its inexorable slide toward
the kind of choppiness that makes UX designers cry quietly at their desks.

Virtual scrolling — also called windowing — solves this by maintaining
only a small pool of rendered DOM nodes regardless of total list length. As the user scrolls,
items leaving the viewport are recycled and repopulated with new data, while the scrollable
container’s height is preserved via a spacer element so the scrollbar behaves normally. The result:
a list of 100,000 items feels — and performs — identically to a list of 100 items.
Advanced implementations
push this further with overscan buffers, scroll-ahead pre-rendering, and dynamic measurement
strategies that accommodate unpredictable content heights.

The temptation is always to defer virtualization: „we’ll add it later when it becomes a problem.”
The problem, of course, arrives before you are ready. Retrofitting virtualization into a mature
component tree is significantly more painful than building it in from the start, which is why
understanding react-list early — even on modest-sized lists — pays compound
dividends as your application scales.

react-list Installation and Project Setup

Getting react-list into your project is refreshingly straightforward. The library
has zero runtime dependencies beyond React itself, which keeps your bundle lean and avoids the
dependency-hell scenarios that larger virtualization libraries can introduce. Install it with your
preferred package manager:

# npm
npm install react-list

# yarn
yarn add react-list

# pnpm
pnpm add react-list

Once installed, the core usage pattern requires three things: a scroll container with a defined
height (or overflow: auto), a ReactList component with an
itemRenderer prop, and an length prop telling the library how many items
exist in total. That is genuinely it for the baseline react-list setup. The
library handles scroll event listeners, position calculations, and DOM recycling internally — you
never touch any of it directly.

One configuration detail that trips up newcomers: the scroll container must have a fixed or
constrained height. If the container grows freely with content, the virtualization logic has no
viewport boundary to work with and will render all items. Set height explicitly, or
use max-height with overflow-y: auto, and the library will behave
exactly as documented. If you need the window itself to be the scroll container, pass
useTranslate3d and configure accordingly — covered in the advanced section below.

Your First react-list Example: Uniform Height Items

The simplest working react-list example renders a list of uniform-height items.
This is the uniform mode — every row is the same height, which allows
react-list to calculate scroll positions with pure arithmetic rather than
measuring the DOM. It is the fastest mode and the right default when your data is homogeneous.

import React from 'react';
import ReactList from 'react-list';

const items = Array.from({ length: 50000 }, (_, i) => `Item ${i + 1}`);

function renderItem(index, key) {
  return (
    <div
      key={key}
      style={{
        height: 48,
        display: 'flex',
        alignItems: 'center',
        padding: '0 16px',
        borderBottom: '1px solid #e5e7eb',
      }}
    >
      {items[index]}
    </div>
  );
}

export default function UniformList() {
  return (
    <div style={{ height: 600, overflow: 'auto' }}>
      <ReactList
        itemRenderer={renderItem}
        length={items.length}
        type="uniform"
      />
    </div>
  );
}

Notice the type="uniform" prop. This is a deliberate signal to the library that it
can safely measure a single item and extrapolate total list height, rather than walking through
every item. In practice this yields near-instant initial render times even at 100,000+ items.
The itemRenderer function receives index and key — use
both; the key is important for React’s reconciliation, and react-list manages it intelligently
across scroll cycles.

You can verify virtualization is working by opening Chrome DevTools, inspecting the scroll
container’s DOM children, and scrolling the list. You will see the rendered node count stay
roughly constant — typically 10–20 items depending on your container height and overscan
configuration — while the transform or top offset of the first rendered
item increments as you scroll. That is the DOM recycling mechanism in action, and
it is what keeps your React scroll performance smooth regardless of dataset size.

Handling Variable Height Items in react-list

Real-world lists are rarely uniform. Comment feeds, chat interfaces, product cards, and news
articles all contain items whose heights depend on their content. This is where
react-list variable height support becomes essential, and where the library’s
design genuinely shines relative to more rigid alternatives. Two modes cover this territory:
variable (heights known ahead of time) and simple (heights measured
from the DOM after render). Choosing correctly between them has meaningful performance
implications.

Use type="variable" when you can compute item height from your data without rendering
it. Provide an itemSizeGetter function that accepts an index and returns a pixel
height. react-list accumulates these values into an offset map, enabling O(log n) binary search
for scroll position lookups. This approach is measurably faster than DOM measurement but requires
your height function to be accurate — a mismatch between predicted and actual height will
produce visible layout glitches, typically manifest as items overlapping or leaving unexpected
gaps.

function getItemHeight(index) {
  // Example: alternating heights based on content type
  return data[index].type === 'featured' ? 120 : 60;
}

<ReactList
  itemRenderer={renderItem}
  itemSizeGetter={getItemHeight}
  length={data.length}
  type="variable"
/>

When heights truly cannot be known without rendering — think markdown content, user-generated text
with inline media, or rich embeds — switch to type="simple". In this mode
react-list renders items, measures them from the DOM, and builds its offset map incrementally as
the user scrolls. The trade-off is a slight layout shift on first render of each item, which is
generally imperceptible at normal scroll speeds but can become noticeable on fast programmatic
scrolls. For most applications, variable with a reasonable height approximation
function delivers the better experience; reserve simple for genuinely unknowable
content dimensions.

Implementing React Infinite Scroll with react-list

Infinite scroll — loading additional data as the user approaches the bottom of the list — pairs
naturally with virtualization because both techniques address the same underlying problem:
displaying arbitrarily large datasets without overwhelming the browser. Combining
React infinite scroll with react-list requires a scroll event listener on the
container that checks proximity to the bottom, then fetches the next page of data and appends it
to your items array. react-list responds automatically to the updated length prop.

import React, { useState, useRef, useCallback } from 'react';
import ReactList from 'react-list';

const PAGE_SIZE = 50;

export default function InfiniteList() {
  const [items, setItems] = useState(() =>
    Array.from({ length: PAGE_SIZE }, (_, i) => `Item ${i + 1}`)
  );
  const [loading, setLoading] = useState(false);
  const containerRef = useRef(null);

  const handleScroll = useCallback(() => {
    const el = containerRef.current;
    if (!el || loading) return;

    const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 200;

    if (nearBottom) {
      setLoading(true);
      // Simulate async data fetch
      setTimeout(() => {
        setItems(prev => [
          ...prev,
          ...Array.from(
            { length: PAGE_SIZE },
            (_, i) => `Item ${prev.length + i + 1}`
          ),
        ]);
        setLoading(false);
      }, 600);
    }
  }, [loading]);

  function renderItem(index, key) {
    return (
      <div key={key} style={{ height: 48, padding: '0 16px', lineHeight: '48px', borderBottom: '1px solid #e5e7eb' }}>
        {items[index]}
      </div>
    );
  }

  return (
    <div
      ref={containerRef}
      onScroll={handleScroll}
      style={{ height: 600, overflow: 'auto' }}
    >
      <ReactList
        itemRenderer={renderItem}
        length={items.length}
        type="uniform"
      />
      {loading && <div style={{ padding: '1rem', textAlign: 'center' }}>Loading...</div>}
    </div>
  );
}

A 200px proximity threshold works well for most applications — it gives your fetch enough lead
time to complete before the user reaches the true bottom, resulting in a seamless experience.
Adjust based on your API latency; high-latency endpoints may warrant a 400–500px trigger zone.
You should also debounce the scroll handler if your data fetch logic is expensive, though the
loading flag in the example above already prevents concurrent requests and covers
most use cases without additional overhead.

One critical subtlety: when appending new items, avoid mutating the existing array. React’s
reconciliation — and react-list’s internal bookkeeping — depends on referential equality checks.
Always create a new array reference (as shown with the spread operator above) to ensure both
React and react-list detect the change and re-render appropriately. Mutation-based updates are a
surprisingly common source of „my list isn’t updating” bug reports on this library.

Advanced react-list Patterns: Overscan, Scroll Restoration, and Programmatic Control

Beyond the baseline configuration, react-list exposes several advanced knobs that meaningfully
affect both performance and user experience.
Advanced list virtualization patterns
typically involve three concerns: controlling how many off-screen items are pre-rendered
(overscan), preserving scroll position across navigation events (scroll restoration), and
programmatically scrolling to a specific index without triggering layout thrash.

The pageSize prop controls overscan behaviour in react-list. It defines how many
items beyond the visible viewport are kept rendered as a buffer — both above and below the current
scroll position. A higher pageSize reduces the chance of the user outrunning the
renderer (seeing a blank flash during very fast scrolls), at the cost of maintaining more DOM
nodes. The default is 10; for data-heavy items with complex sub-trees, consider dropping it to
5–7 to reduce memory pressure. For simple text rows, 15–20 provides noticeably smoother fast
scrolling at negligible cost.

For programmatic scroll control, react-list exposes a ref API with scrollTo(index)
and scrollAround(index) methods. The former scrolls the target index to the top of
the viewport; the latter scrolls only as far as needed to bring the item into view — the right
choice for „jump to result” UX where you want to respect the user’s current position. Access these
via a ref attached to the ReactList component and call them imperatively. This is
also the mechanism for implementing scroll restoration: store the visible index range on unmount
(via getVisibleRange()) and call scrollTo on remount.

import { useRef } from 'react';

function ControlledList() {
  const listRef = useRef(null);

  function jumpToIndex(index) {
    listRef.current?.scrollTo(index);
  }

  function scrollIntoView(index) {
    listRef.current?.scrollAround(index);
  }

  return (
    <>
      <button onClick={() => jumpToIndex(999)}>Jump to item 1000</button>
      <div style={{ height: 600, overflow: 'auto' }}>
        <ReactList
          ref={listRef}
          itemRenderer={renderItem}
          length={10000}
          type="uniform"
        />
      </div>
    </>
  );
}

react-list vs react-window vs react-virtualized: Choosing the Right Tool

The React ecosystem offers three main virtualization options, and choosing between them is less
about raw performance (all three are well-optimised) and more about API philosophy and
flexibility. react-virtualized is the original, feature-richest option — it
supports lists, grids, masonry layouts, table components, and more. The cost is complexity: the
API surface is large, bundle size is non-trivial, and learning curve is real. It remains the
right choice for complex data-grid requirements where you need the full feature set.

react-window is the spiritual successor from the same author (Brian Vaughn),
stripped down to the essentials: fixed-size lists, variable-size lists, fixed-size grids. The
API is clean and opinionated, bundle size is minimal, and for straightforward use cases it
requires less configuration than either alternative. Its limitation is intentional: it does not
support dynamic measurement, and its scroll container model requires wrapping your list in a
specifically-sized outer element, which can conflict with certain layout systems.

react-list occupies a pragmatic middle ground. It is more flexible than
react-window (three height modes, including DOM-measured dynamic heights), simpler than
react-virtualized (focused on lists rather than grids and tables), and its scroll container model
is less prescriptive — making it easier to drop into existing layouts. The
React large list rendering performance across all three libraries is comparable
in benchmarks; the decision ultimately comes down to what your specific layout and content
requirements demand. If you need uniform or variable-but-calculable heights in a standard scroll
container: react-list. If you need full grid/table virtualization: react-virtualized. If you
prefer a minimal API and your heights are fixed: react-window.

React Performance Optimization: Getting the Most from react-list

Virtualization handles the DOM count problem, but it does not automatically solve re-render
overhead within each list item. If your itemRenderer returns a complex component
that re-renders on every parent state change, you will still accumulate unnecessary work — just
spread across fewer nodes. Wrap your item component in React.memo and ensure prop
stability (memoize callbacks with useCallback, stabilize object references with
useMemo) to prevent cascading re-renders on scroll events that update container
state.

The itemRenderer function itself should be defined outside the render path or
wrapped in useCallback. A freshly created function reference on every render will
cause react-list to treat it as a configuration change and perform unnecessary internal
recalculations. This is one of the most common performance pitfalls reported by developers who
install the library, measure no improvement, and conclude that virtualization „doesn’t work” —
when in fact the bottleneck shifted to renderer instability. Profile with React DevTools Profiler
before optimising; it will show you exactly which components are re-rendering and why.

Quick performance checklist for react-list:

  • Use type="uniform" whenever item heights are consistent — it is the fastest mode by a meaningful margin.
  • Stabilize itemRenderer with useCallback or module-level definition.
  • Wrap item components in React.memo with proper prop comparison.
  • Avoid reading from context inside itemRenderer unless the context value is stable.
  • For dynamic content, prefer type="variable" with an approximation function over type="simple" with DOM measurement — faster and more predictable.

One underappreciated optimization: keep your data array reference stable between renders. If your
items array is reconstructed on every render cycle (a common pattern when filtering or sorting
inline), react-list will recalculate its internal geometry on every update, regardless of whether
the visible items actually changed. Move filtering and sorting logic into useMemo so
the derived array only changes when the underlying data or filter criteria change. Combined with
stable renderer references, this produces the smoothest possible React scroll
performance
at any dataset size.

Frequently Asked Questions

How does react-list improve scroll performance in React?

react-list implements a windowing (virtualization) technique that renders only
the list items currently visible in the viewport, plus a configurable overscan buffer above and
below. Instead of mounting 50,000 DOM nodes simultaneously, the library maintains a small, fixed
pool of nodes and repopulates them with new data as the user scrolls. This keeps active DOM node
count constant regardless of list length, dramatically reducing memory consumption, eliminating
layout recalculation overhead, and maintaining 60fps scroll performance on datasets that would
otherwise bring the browser to its knees.

How do I handle variable height items in react-list?

Set the type prop to "variable" and provide an
itemSizeGetter function that accepts an item index and returns its pixel height as
a number. react-list uses these values to build a cumulative offset map, enabling accurate
scroll position calculations without measuring the DOM. If item heights cannot be determined
ahead of time, use type="simple" instead — the library will measure each item
from the DOM after its first render and adjust positions accordingly, at the cost of minor
initial layout shifts.

What is the difference between react-list and react-window?

react-window is a minimal, opinionated virtualization library focused on
fixed-size and variable-size lists and grids. Its API is deliberately small, its bundle footprint
tiny, and its scroll container model requires a specific outer-wrapper setup. react-list
is more flexible: it supports three height modes (uniform, variable, simple/dynamic), its scroll
container model is less prescriptive, and it is generally easier to integrate into existing
component hierarchies without restructuring. For simple, fixed-height lists, react-window is
an equally valid choice; for variable or dynamic content heights and more complex integration
scenarios, react-list typically requires less adaptation work.

Closing Thoughts

react-list is one of those libraries that asks very little of you and returns
disproportionate value. A handful of props, a stable item renderer, and the right
type configuration will transform a list that made senior engineers wince into one
that scrolls like silk at any dataset size. The real work is not in learning the API — it is in
understanding why each configuration choice exists, so you can make intelligent decisions
when your data does something unexpected.

Start with type="uniform" for new projects where you control item heights. Move to
type="variable" when content complexity demands it. Reach for the ref API only when
you have a clear need for programmatic control. Profile before optimising, memoize deliberately
rather than reflexively, and keep your data references stable. Follow these principles and
React large list rendering will quietly stop being a problem category in your
applications — which is, frankly, the best possible outcome for any library to achieve.


Semantic core used in this article:
react-list · React list virtualization · react-list tutorial · React virtualized list ·
react-list installation · React performance optimization · react-list example ·
React infinite scroll · react-list setup · React large list rendering ·
react-list variable height · React scroll performance · react-list advanced ·
React list component · react-list getting started · windowing technique ·
virtual scrolling · DOM recycling · viewport rendering · overscan · itemSizeGetter ·
react-window alternative · react-virtualized alternative · lazy rendering