A small React app for learning list virtualization with react-window.
- Node.js 18+ (installed via Homebrew:
brew install node)
cd react-virtualization-demo
npm install # first time only
npm run dev # start dev server → open the URL shown in the terminalsrc/App.jsx— virtualized 3-column grid of 100 movie cardssrc/components/MovieCard.jsx— single card (poster placeholder + title/meta)
A beginner-friendly explanation of virtualization using react-window.
Virtualization is a performance optimization technique where only visible UI elements are rendered in the DOM instead of rendering the entire dataset.
Example:
- Instead of rendering 10,000 cards,
- only visible cards are rendered,
- and new cards are mounted dynamically during scroll.
- Smaller DOM
- Faster rendering
- Smooth scrolling
- Lower memory usage
- react-window
- react-virtualized
import { FixedSizeList as List } from "react-window";List is the virtualization engine.
It acts like a smart scroll container.
- Detect visible rows
- Render only visible rows
- Remove non-visible rows
- Handle scrolling efficiently
- Calculate row positions
const Row = ({ index, style }) => {Row is a reusable component responsible for rendering one horizontal row.
Example:
- If each row contains 3 movie cards,
- then one
Rowrenders 3 movies.
--------------------------------
| Movie 1 | Movie 2 | Movie 3 |
--------------------------------Each horizontal strip is one row.
const Row = ({ index, style })react-window automatically passes a style object to every row.
Example:
{
position: "absolute",
top: 580,
left: 0,
height: 580,
width: "100%"
}These styles are NOT mainly for visual styling.
They are required for virtualization because:
- rows are manually positioned,
- not naturally stacked by browser layout.
position: absolutetopheightwidth
Without these styles:
- rows overlap,
- scrolling breaks,
- virtualization fails.
style={{
...style
}}This copies all styles provided by react-window.
Equivalent to:
position: style.position,
top: style.top,
height: style.height,
width: style.widthWithout spreading the style:
- rows render at same position,
- virtualization positioning fails.
Normally browser stacks elements naturally:
Row 1
Row 2
Row 3But virtualization manually positions rows for performance.
Example:
{
position: "absolute",
top: 1160
}This means:
- place this row exactly at 1160px from top.
top tells browser where the row should appear vertically.
top = rowIndex * itemSize| Row | top |
|---|---|
| 0 | 0 |
| 1 | 580 |
| 2 | 1160 |
height: 580Defines vertical space occupied by each row.
Needed for:
- scrolling calculations
- visible row detection
- positioning
width: "100%"Makes row occupy full container width.
itemCount={Math.ceil(MOVIES.length / COLUMN_COUNT)}itemCount means:
- total number of rows,
- NOT total number of movies.
- 100 movies
- 3 movies per row
Math.ceil(100 / 3)34 rowsSo List internally thinks:
Row 0
Row 1
Row 2
...
Row 33Using:
itemCount = 34
itemSize = 580It calculates total scroll height:
34 * 580 = 19720pxThis creates a natural scrollbar even though only visible rows are rendered.
itemSize={580}Means:
- every row height is fixed to 580px.
This is extremely important.
List uses this to:
- calculate row positions,
- detect visible rows,
- optimize rendering.
top = rowIndex * itemSizeSuppose:
scrollTop = 1200
itemSize = 5801200 / 580 ≈ row 2So:
- Row 2 is visible,
- render Row 2 onward.
Only visible rows stay mounted.
Because rows have fixed height:
Listcan calculate positions mathematically,- without measuring actual DOM elements.
This makes virtualization extremely fast.
<div
style={{
...style,
height: style.height - 20,
paddingBottom: "20px",
boxSizing: "border-box",
}}
>Suppose original height:
580Now reduced to:
560This creates spacing between rows.
Adds bottom spacing visually.
Without reducing height first:
- total row size becomes incorrect,
- virtualization calculations break.
So:
- reduce height,
- then add padding.
Final visual size remains correct.
Normally:
final height = content + paddingWith border-box:
- padding stays inside defined height.
This keeps row size accurate.
<List
height={window.innerHeight - 140}
itemCount={34}
itemSize={580}
width={"100%"}
>
{Row}
</List>Visible viewport height.
window.innerHeight = 900
height = 760Meaning:
- only 760px visible,
- remaining content scrolls.
Total number of rows.
34 rowsFixed height of each row.
580pxWidth of virtualized container.
100%This is the row renderer component.
Internally List does:
<Row index={0} style={...} />
<Row index={1} style={...} />Only for visible rows.
-
Create a
Rowcomponent. -
Each row is responsible for rendering a group of items, such as 3 movie cards per row.
-
react-windowpasses astyleobject to every row. -
That style contains positioning properties like:
- absolute positioning
- top
- height
- width
-
These styles are required for virtualization because they help
react-windowcorrectly position rows inside the scroll container. -
Inside the row:
slice()is used to get items belonging to that row,map()is used to render those items.
-
Then the
Listcomponent from react-window is used. -
Listacts as a virtualized scroll container. -
It receives props like:
heightitemCountitemSizewidth
-
Listcontinuously checks which rows are currently visible inside the viewport. -
Only visible rows are mounted in the DOM.
-
When the user scrolls:
- rows leaving the viewport are unmounted,
- new visible rows are mounted dynamically.
- This keeps the DOM small and improves rendering and scrolling performance significantly.