React Window: Build Fast Virtualized Lists and Improve Scroll Performance
Why use react-window for virtualized lists?
Rendering thousands of rows in a React app kills frame rates and increases memory usage. react-window offers a tiny, battle-tested approach to windowing: render only the DOM nodes visible in the viewport. This reduces paint cost, shrinks the reconciliation work, and speeds up scroll performance without changing your data model.
react-window is intentionally minimal. It focuses on list virtualization primitives like FixedSizeList and VariableSizeList, leaving layout and styling to you. For many UIs, that minimalism means fewer surprises and lower bundle overhead compared to heavier libraries.
Use react-window when: you have long lists or grids, you need near-native scroll responsiveness, or you want a lightweight alternative to full-featured solutions. If you need advanced features (cell measurement, dynamic virtualization across zones), consider complementing react-window with small helper utilities rather than swapping to a monolith.
How react-window works (concepts and internals)
At its core, react-window computes which items intersect the viewport and renders only those items. It uses fixed or measured item sizes to map scroll position to item index quickly. For fixed sizes, the math is constant time; for variable sizes, it keeps a small cache of item measurements to compute ranges efficiently.
Virtualization separates the data array from DOM nodes. Your data source can remain the same (an array of 50k objects), but DOM nodes are reused and remounted only for visible indexes. This reduces layout thrash and lowers the memory footprint for large-list rendering.
Because it avoids heavy layout reads and minimizes reconciliation, react-window often leads to measurable improvements in frames-per-second on scroll and lower long-task CPU times. However, it relies on predictable sizing or on a strategy to measure sizes, so choose FixedSizeList when possible for the best performance.
Getting started: installation and basic setup
Install react-window from npm and include it in your component. The package is small and integrates smoothly with React and TypeScript projects:
npm install react-window
# or
yarn add react-windowThen create a simple fixed-size list. Import the primitive and render an item renderer (row) that receives an index and style. The style prop is required — it positions each item correctly inside the scrolling container.
import { FixedSizeList as List } from 'react-window';
function Row({ index, style }) {
return <div style={style}>Row {index}</div>;
}
<List
height={400}
itemCount={1000}
itemSize={35}
width={'100%'}
>
{Row}
</List>Important: always pass the provided style object to each row; otherwise, items will overlap. For more setup tips and an expanded tutorial, see this practical react-window tutorial.
FixedSizeList vs VariableSizeList — when to choose which
FixedSizeList is the simplest and fastest option: every item has the same pixel height (or width in horizontal mode). Because measurement is constant, the library performs O(1) calculations to determine visible ranges — ideal for huge lists.
VariableSizeList supports items with varying heights. It keeps a measurement cache and approximates positions; calculations are slightly more complex and slower than fixed-size math. Use it when items legitimately vary in size and you cannot normalize them via CSS or content truncation.
If possible, prefer FixedSizeList for raw performance. When heights vary slightly, consider forcing a uniform height, using CSS line-clamp, or rendering a compact variant to reduce complexity. When dynamic heights are unavoidable, combine VariableSizeList with measurement helpers or an item-size cache to keep performance predictable.
Implementing infinite scroll and large-list rendering strategies
react-window doesn’t include a built-in infinite-loader, but you can implement infinite scroll with scroll position listeners and a little orchestration: detect when the bottom buffer is visible and fetch the next page. Keep your data outside the list (in state or a store) and update itemCount when new items arrive.
One common pattern is to reserve a sentinel index near the end and trigger loading when that index enters the rendered range. Use lightweight flags to avoid duplicate requests and provide a placeholder row (loading indicator) at the end while data arrives.
If you prefer an out-of-the-box solution, combine react-window with the react-window-infinite-loader utility or implement a simple hook that watches the onItemsRendered callback to determine when to load more. This keeps scroll performance high while handling large-list paging.
Performance optimization patterns and common pitfalls
Optimize render performance by memoizing row components with React.memo and preventing expensive computations in render. Row renderers should be as lightweight as possible — pass only minimal props and avoid inline functions that change on every render.
Beware of layout thrash: avoid reading DOM properties in render or forcing reflows. If item content requires measurement (e.g., images or rich HTML), measure once and cache the sizes, then update the list via resetAfterIndex APIs to recompute offsets without rerendering everything.
Virtualization exposes a few UX edge cases: keyboard focus and accessibility (focusable elements may be unmounted when scrolled out). Provide stable keys, manage focus manually when needed, and consider ARIA attributes for screen readers. For complex accessibility scenarios, test with assistive tech or provide a non-virtualized fallback for small lists.
Practical examples and code patterns
Below are concise patterns that cover common needs: memoized rows, infinite loading, and variable-size measurement. These snippets are minimal; adapt them to your state management and data layer.
// Memoized row
const Row = React.memo(({ index, style, data }) => {
const item = data[index];
return <div style={style}>{item.title}</div>;
});To implement infinite loading, attach a callback to onItemsRendered and request more items when the rendered range reaches the end:
function onItemsRendered({ visibleStopIndex }) {
if (visibleStopIndex >= items.length - 1 && !isLoading) {
loadMore();
}
}For variable heights, measure off-DOM or use ResizeObserver to capture item size and call resetAfterIndex(index) to update offsets efficiently without a full reflow.
Debugging, testing, and instrumentation
When debugging scrolling issues, log the onScroll and onItemsRendered callbacks to verify which indexes are computed. Use browser performance tools (Performance tab) to capture frame rates during scroll and identify long tasks or layout thrash.
Test edge cases: very short lists (ensure virtualization gracefully falls back), dynamic inserts at the top (maintain scroll offset if needed), and keyboard navigation. Add unit tests for your item renderer and integration tests to ensure data loads correctly when the sentinel row is reached.
For production monitoring, instrument time-to-first-render for list views and track dropped frames on key pages. These metrics will quickly indicate regressions when changing item renderers or adding complex content inside rows.
Resources and further reading
Official react-window repository and docs: react-window. For general React performance patterns see the React docs on performance.
Hands-on tutorials and examples are useful. This practical walkthrough is a good complement: react-window tutorial. If you previously used react-virtualized, note that react-window is lighter and simpler for many list-only use cases.
Semantic Core (keyword clusters)
The following semantic core groups the primary, secondary, and clarifying keywords and LSI phrases used in this article. Use these for on-page optimization, internal linking, and anchor text.
Primary keywords
- react-window
- React window virtualization
- react-window tutorial
- react-window installation
- React large list rendering
Secondary keywords
- FixedSizeList
- VariableSizeList
- React virtualized list
- React scroll performance
- react-window example
- react-window setup
Clarifying / long-tail & LSI phrases
- React performance optimization
- React infinite scroll with react-window
- large list rendering in React
- virtualized list component
- onItemsRendered, resetAfterIndex
Selected FAQ
What is react-window and when should I use it?
react-window is a lightweight library for windowing (virtualizing) long lists and grids in React. Use it when you render large datasets (hundreds or thousands of rows) and need to improve scroll performance and reduce DOM overhead. Prefer FixedSizeList for consistent heights and VariableSizeList when item sizes vary.
How do I install and set up react-window?
Install with npm install react-window or yarn add react-window. Import the desired primitive (e.g., FixedSizeList) and render your row component while passing the required style prop into each item. See the example in this article for a minimal setup.
How can I implement infinite scroll with react-window?
Use the onItemsRendered callback to detect when the rendered end index approaches your loaded item count, then trigger a paginated fetch. Manage loading state to prevent duplicate requests, and append new items to the data source. Optionally, use the react-window-infinite-loader utility for a packaged pattern.
