react-virtualized vs react-infinite-scroller vs react-window
Rendering Large Lists in React: Legacy, Power, and Performance
react-virtualizedreact-infinite-scrollerreact-windowSimilar Packages:

Rendering Large Lists in React: Legacy, Power, and Performance

react-infinite-scroller, react-virtualized, and react-window are all React libraries designed to handle the rendering of large datasets, but they approach the problem with different strategies and levels of abstraction. react-infinite-scroller focuses on the user interaction pattern of infinite scrolling, loading more data as the user reaches the bottom of a list, often rendering all accumulated items in the DOM. react-virtualized is a comprehensive suite of high-performance components that use windowing (virtualization) to render only the visible rows, offering complex features like dynamic heights and grid layouts. react-window is the modern, lightweight successor to react-virtualized, created by the same author to provide a smaller bundle size and a simpler API for standard virtualization needs, though with fewer built-in complex features.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-virtualized1,249,50727,0752.24 MB02 years agoMIT
react-infinite-scroller03,29530.3 kB98-MIT
react-window017,207216 kB110 days agoMIT

Rendering Large Lists in React: Legacy, Power, and Performance

When building data-heavy dashboards, chat interfaces, or feeds, rendering thousands of DOM nodes at once will freeze your browser. The solution is to render only what the user can see. Three libraries have defined this space in the React ecosystem: react-infinite-scroller, react-virtualized, and react-window. While they all solve the "big list" problem, they do so with very different philosophies and trade-offs.

🏗️ Core Architecture: DOM Rendering Strategies

The most critical difference lies in how many DOM nodes each library creates. This decision dictates your application's memory usage and scroll performance.

react-infinite-scroller does not virtualize. It renders every single item you load into the actual DOM. It simply listens for scroll events to tell you when to fetch more data. If you load 5,000 items, you have 5,000 DOM nodes. This works fine for small lists but causes significant lag as the list grows.

// react-infinite-scroller: Renders ALL items in the DOM
import InfiniteScroll from 'react-infinite-scroller';

function Feed() {
  const [items, setItems] = useState(initialItems);

  const loadMore = () => {
    // Fetch next page and append to existing list
    setItems(prev => [...prev, ...newItems]);
  };

  return (
    <InfiniteScroll
      pageStart={0}
      loadMore={loadMore}
      hasMore={true}
    >
      {items.map(item => (
        <div key={item.id} className="item">
          {item.content}
        </div>
      ))}
    </InfiniteScroll>
  );
}

react-virtualized uses windowing. It calculates which items are visible in the viewport and renders only those, plus a small buffer. It uses absolute positioning to place these items in a container that matches the full height of the dataset. The DOM stays light regardless of data size.

// react-virtualized: Renders ONLY visible items
import { List } from 'react-virtualized';

function Feed({ items }) {
  const rowRenderer = ({ key, index, style }) => (
    <div key={key} style={style} className="item">
      {items[index].content}
    </div>
  );

  return (
    <List
      width={600}
      height={400}
      rowCount={items.length}
      rowHeight={50}
      rowRenderer={rowRenderer}
    />
  );
}

react-window also uses windowing but with a simpler, tree-shakable architecture. Instead of importing a massive List component, you import a lightweight FixedSizeList or VariableSizeList. It functions similarly to react-virtualized under the hood but strips away legacy baggage for a cleaner API.

// react-window: Renders ONLY visible items (Modern API)
import { FixedSizeList } from 'react-window';

function Feed({ items }) {
  const Row = ({ index, style }) => (
    <div style={style} className="item">
      {items[index].content}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={600}
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}

📏 Handling Dynamic Row Heights

One of the hardest problems in virtualization is handling rows that change height based on their content (e.g., a tweet with 1 line of text vs. one with an image and 10 lines).

react-infinite-scroller handles this naturally because it renders everything. The browser's standard layout engine manages the heights automatically. No special configuration is needed.

// react-infinite-scroller: Natural height handling
// No extra code needed; CSS determines height
<div className="item">
  {longContent} {/* Height expands automatically */}
</div>

react-virtualized solves this with CellMeasurer. This component measures the content of a row after it renders and caches the height. The list then uses this cached height for positioning. It is powerful but requires boilerplate code and a cache reference.

// react-virtualized: Complex dynamic heights
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';

const cache = new CellMeasurerCache({
  fixedWidth: true,
  defaultHeight: 100,
});

const rowRenderer = ({ index, key, parent, style }) => (
  <CellMeasurer
    key={key}
    cache={cache}
    parent={parent}
    columnIndex={0}
    rowIndex={index}
  >
    <div style={style}>
      {items[index].content}
    </div>
  </CellMeasurer>
);

react-window offers VariableSizeList. You must provide an itemSize function that returns the height for a given index. Crucially, you must manually tell the list when a row's height changes so it can recalculate positions. This gives you control but puts the burden of tracking state on you.

// react-window: Controlled dynamic heights
import { VariableSizeList } from 'react-window';

const listRef = useRef(null);

// Tell the list a specific row changed height
listRef.current.resetAfterIndex(index);

const Row = ({ index, style }) => (
  <div style={style}>
    {items[index].content}
  </div>
);

<VariableSizeList
  ref={listRef}
  height={400}
  width={600}
  itemCount={items.length}
  itemSize={index => getItemHeight(items[index])} // You define this logic
>
  {Row}
</VariableSizeList>

🧩 Feature Set and Extensibility

Beyond basic lists, real-world apps often need grids, infinite loading triggers, or sticky headers.

react-infinite-scroller is specialized for the "infinite" pattern. It provides props like initialLoad, loadMore, and hasMore out of the box. It does not support grids or complex layouts natively; you would need to build those yourself or combine it with other tools.

// react-infinite-scroller: Built-in infinite logic
<InfiniteScroll
  loadMore={loadMore}
  hasMore={hasMore}
  loader={<h4 key={0}>Loading...</h4>}
>
  {/* Items */}
</InfiniteScroll>

react-virtualized is a "kitchen sink" library. It includes Grid, Table, AutoSizer (to fill parent width/height), and InfiniteLoader (to handle data fetching logic). The InfiniteLoader component wraps your list and manages the logic of requesting new pages, which is more robust than the simple callback in react-infinite-scroller.

// react-virtualized: Integrated InfiniteLoader
import { InfiniteLoader, List } from 'react-virtualized';

<InfiniteLoader
  isRowLoaded={({ index }) => !!items[index]}
  loadMoreRows={loadMoreRows}
  rowCount={totalRowCount}
>
  {({ onRowsRendered, registerChild }) => (
    <List
      ref={registerChild}
      onRowsRendered={onRowsRendered}
      // ... other props
    />
  )}
</InfiniteLoader>

react-window is minimalist. It does not include InfiniteLoader or AutoSizer in the core package. Instead, the author publishes them as separate companion packages (react-window-infinite-loader, react-virtualized-auto-sizer). This keeps the core bundle tiny but requires you to install and wire up more dependencies for advanced features.

// react-window: Composable via separate packages
import { FixedSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import AutoSizer from 'react-virtualized-auto-sizer';

<AutoSizer>
  {({ height, width }) => (
    <InfiniteLoader
      isItemLoaded={isItemLoaded}
      loadMoreItems={loadMoreItems}
      itemCount={itemCount}
    >
      {({ onItemsRendered, ref }) => (
        <FixedSizeList
          height={height}
          width={width}
          itemCount={itemCount}
          itemSize={50}
          onItemsRendered={onItemsRendered}
          ref={ref}
        >
          {Row}
        </FixedSizeList>
      )}
    </InfiniteLoader>
  )}
</AutoSizer>

🛠️ Maintenance and Future Proofing

Choosing a library is also a bet on its future.

react-infinite-scroller sees infrequent updates. While it still works, the fundamental approach of rendering all DOM nodes is increasingly seen as an anti-pattern for large datasets in modern React development. It lacks support for newer React features like concurrent rendering optimizations.

react-virtualized is officially in maintenance mode. The creator, Brian Vaughn, has stated that no new features will be added. It relies on older React patterns (like componentDidMount and legacy context) which can cause warnings or issues in strict mode or future React versions. It remains stable but is effectively frozen.

react-window is the active, recommended path. It was built specifically to address the bundle size and complexity issues of react-virtualized. It uses modern React hooks and patterns, ensuring compatibility with React 18 and beyond. It receives regular updates and bug fixes.

📊 Summary: Key Differences

Featurereact-infinite-scrollerreact-virtualizedreact-window
RenderingFull DOM (No Virtualization)Windowed (Virtualized)Windowed (Virtualized)
PerformancePoor with 1000+ itemsExcellentExcellent
Bundle SizeSmallLarge (~100kb+)Tiny (~15kb)
Dynamic HeightsAutomatic (Native)Complex (CellMeasurer)Manual (resetAfterIndex)
Infinite LoadBuilt-in PropsInfiniteLoader ComponentSeparate Package
StatusLegacyMaintenance ModeActive / Recommended

💡 The Big Picture

react-infinite-scroller is a legacy tool. Avoid it for new development unless you are strictly constrained by an existing codebase that cannot tolerate a refactor. The performance cost of rendering thousands of DOM nodes is too high for modern web standards.

react-virtualized is the heavy-duty tank. It solved hard problems years ago and is incredibly stable. However, its weight and lack of active development make it a poor choice for greenfield projects. Use it only if you need a specific, complex feature (like a very specific grid layout) that react-window cannot easily replicate.

react-window is the modern standard. It provides the performance benefits of virtualization with a fraction of the code. Its modular design encourages best practices, and its active maintenance ensures it will work smoothly with future React updates. For 95% of use cases — from simple dropdowns to massive data grids — react-window is the correct architectural choice.

How to Choose: react-virtualized vs react-infinite-scroller vs react-window

  • react-virtualized:

    Choose react-virtualized if your project requires complex, non-standard list behaviors that react-window does not support out of the box, such as intricate dynamic height calculations with CellMeasurer or specific sticky section headers in older React versions. Be aware that this library is in maintenance mode, has a larger bundle size, and uses a more verbose API compared to modern alternatives.

  • react-infinite-scroller:

    Choose react-infinite-scroller only if you are maintaining a legacy codebase that relies on its specific 'load more' trigger logic and you cannot afford a refactor. It is generally not recommended for new projects because it renders all items in the DOM, which leads to severe performance degradation and memory issues as the list grows beyond a few hundred items.

  • react-window:

    Choose react-window for almost all new projects requiring high-performance lists or grids. It offers the best balance of small bundle size, modern API design, and speed. It is the ideal choice when you need to render thousands of items efficiently and your row heights are either fixed or can be estimated reliably without heavy measurement overhead.

README for react-virtualized

React virtualized

React components for efficiently rendering large lists and tabular data. Check out the demo for some examples.

If you like this project, 🎉 become a sponsor or ☕ buy me a coffee

Sponsors

The following wonderful companies have sponsored react-virtualized:

Learn more about becoming a sponsor!

A word about react-window

If you're considering adding react-virtualized to a project, take a look at react-window as a possible lighter-weight alternative. Learn more about how the two libraries compare here.

Getting started

Install react-virtualized using npm.

npm install react-virtualized --save

ES6, CommonJS, and UMD builds are available with each distribution. For example:

// Most of react-virtualized's styles are functional (eg position, size).
// Functional styles are applied directly to DOM elements.
// The Table component ships with a few presentational styles as well.
// They are optional, but if you want them you will need to also import the CSS file.
// This only needs to be done once; probably during your application's bootstrapping process.
import 'react-virtualized/styles.css';

// You can import any component you want as a named export from 'react-virtualized', eg
import {Column, Table} from 'react-virtualized';

// But if you only use a few react-virtualized components,
// And you're concerned about increasing your application's bundle size,
// You can directly import only the components you need, like so:
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import List from 'react-virtualized/dist/commonjs/List';

Note webpack 4 makes this optimization itself, see the documentation.

If the above syntax looks too cumbersome, or you import react-virtualized components from a lot of places, you can also configure a Webpack alias. For example:

// Partial webpack.config.js
{
  alias: {
    'react-virtualized/List': 'react-virtualized/dist/es/List',
  },
  ...rest
}

Then you can just import like so:

import List from 'react-virtualized/List';

// Now you can use <List {...props} />

You can also use a global-friendly UMD build:

<link rel="stylesheet" href="path-to-react-virtualized/styles.css" />
<script src="path-to-react-virtualized/dist/umd/react-virtualized.js"></script>

Now you're ready to start using the components. You can learn more about which components react-virtualized has to offer below.

Dependencies

React Virtualized has very few dependencies and most are managed by NPM automatically. However the following peer dependencies must be specified by your project in order to avoid version conflicts: react, react-dom. NPM will not automatically install these for you but it will show you a warning message with instructions on how to install them.

Pure Components

By default all react-virtualized components use shallowCompare to avoid re-rendering unless props or state has changed. This occasionally confuses users when a collection's data changes (eg ['a','b','c'] => ['d','e','f']) but props do not (eg array.length).

The solution to this is to let react-virtualized know that something external has changed. This can be done a couple of different ways.

Pass-thru props

The shallowCompare method will detect changes to any props, even if they aren't declared as propTypes. This means you can also pass through additional properties that affect cell rendering to ensure changes are detected. For example, if you're using List to render a list of items that may be re-sorted after initial render- react-virtualized would not normally detect the sort operation because none of the properties it deals with change. However you can pass through the additional sort property to trigger a re-render. For example:

<List {...listProps} sortBy={sortBy} />
Public methods

Grid and Collection components can be forcefully re-rendered using forceUpdate. For Table and List, you'll need to call forceUpdateGrid to ensure that the inner Grid is also updated. For MultiGrid, you'll need to call forceUpdateGrids to ensure that the inner Grids are updated.

Documentation

API documentation available here.

There are also a couple of how-to guides:

Examples

Examples for each component can be seen in the documentation.

Here are some online demos of each component:

And here are some "recipe" type demos:

Supported Browsers

react-virtualized aims to support all evergreen browsers and recent mobile browsers for iOS and Android. IE 9+ is also supported (although IE 9 will require some user-defined, custom CSS since flexbox layout is not supported).

If you find a browser-specific problem, please report it along with a repro case. The easiest way to do this is probably by forking this Plunker.

Friends

Here are some great components built on top of react-virtualized:

  • react-infinite-calendar: Infinite scrolling date-picker with localization, themes, keyboard support, and more
  • react-sortable-hoc: Higher-order components to turn any list into an animated, touch-friendly, sortable list
  • react-sortable-tree: Drag-and-drop sortable representation of hierarchical data
  • react-virtualized-checkbox: Checkbox group component with virtualization for large number of options
  • react-virtualized-select: Drop-down menu for React with windowing to support large numbers of options.
  • react-virtualized-tree: A reactive tree component that aims to render large sets of tree structured data in an elegant and performant way
  • react-timeline-9000: A calendar timeline component that is capable of displaying and interacting with a large number of items

Contributions

Use GitHub issues for requests.

I actively welcome pull requests; learn how to contribute.

Changelog

Changes are tracked in the changelog.

License

react-virtualized is available under the MIT License.