Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to optimize large lists in React

Rendering thousands of DOM nodes in a React list causes severe performance degradation — slow initial render, janky scrolling, and high memory usage. As the creator of CoreUI with 25 years of front-end development experience, I’ve optimized data-heavy tables and lists in CoreUI dashboards that display thousands of rows, and virtualization is the single most impactful technique. The approach with react-window renders only the rows visible in the viewport, keeping the DOM node count constant regardless of data size. A list with 10,000 items renders the same ~20 DOM nodes as a list with 10 items when virtualized.

Install react-window and create a virtualized list.

npm install react-window
// VirtualList.jsx
import { FixedSizeList } from 'react-window'

const ITEM_HEIGHT = 50

function Row({ index, style, data }) {
  const item = data[index]
  return (
    // style must be applied to the row for positioning
    <div style={style} className="list-row">
      <span>{item.id}</span>
      <span>{item.name}</span>
      <span>{item.email}</span>
    </div>
  )
}

export function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={500}        // Visible height of the list container
      itemCount={items.length}
      itemSize={ITEM_HEIGHT}
      width="100%"
      itemData={items}    // Pass data via itemData for stable reference
    >
      {Row}
    </FixedSizeList>
  )
}

FixedSizeList assumes all rows have the same height, making it the most performant option. The style prop from react-window positions each row absolutely — always apply it to the row element. itemData passes your data array without creating a new closure on each render.

Variable Height Rows

Use VariableSizeList when rows have different heights.

import { VariableSizeList } from 'react-window'

const ITEM_SIZES = [40, 80, 55, 120, 40] // Different heights

function VariableRow({ index, style, data }) {
  return (
    <div style={style} className="list-row">
      {data[index].content}
    </div>
  )
}

export function VariableList({ items }) {
  const getItemSize = (index) => ITEM_SIZES[index % ITEM_SIZES.length]

  return (
    <VariableSizeList
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
      itemData={items}
    >
      {VariableRow}
    </VariableSizeList>
  )
}

itemSize is a function that returns the height for each index. If you don’t know item heights in advance, measure them with ResizeObserver and pass the measured heights.

Virtualized Grid (Tables)

Use FixedSizeGrid for tabular data with many columns.

import { FixedSizeGrid } from 'react-window'

function Cell({ columnIndex, rowIndex, style, data }) {
  return (
    <div style={style} className="grid-cell">
      {data[rowIndex][columnIndex]}
    </div>
  )
}

export function VirtualTable({ rows, columns }) {
  return (
    <FixedSizeGrid
      columnCount={columns.length}
      columnWidth={150}
      height={500}
      rowCount={rows.length}
      rowHeight={40}
      width={800}
      itemData={rows}
    >
      {Cell}
    </FixedSizeGrid>
  )
}

FixedSizeGrid virtualizes both rows and columns, making it suitable for wide spreadsheet-like tables with thousands of rows and many columns.

Best Practice Note

This is the same virtualization approach used in CoreUI React data table templates for handling large datasets. Pair virtualization with React.memo on row components to prevent re-renders when unrelated state changes. For tables that need sorting, filtering, and pagination, consider TanStack Table (react-table) which integrates well with react-window and handles the data transformation layer. See how to prevent unnecessary re-renders in React for complementary rendering optimizations.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author