Optimizing Large List Rendering in React
Rendering large lists without virtualization is a common performance pitfall that can lead to sluggish user interfaces, especially when dealing with hundreds or thousands of items.
The Problem: Unoptimized List Rendering
// ❌ Performance-heavy approach
function BigList({ items }) {
return (
<ul>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
}
Why This is Problematic
- DOM Overload: All list items are rendered in the DOM simultaneously
- Memory Usage: High memory consumption from many component instances
- Slow Scrolling: Browser struggles with layout and repaints
- Wasted Renders: Components off-screen still consume resources
Solution: List Virtualization
1. Using React Window (Recommended)
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
return (
<List
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ListItem item={items[index]} />
</div>
)}
</List>
);
}
2. With Dynamic Item Sizes (react-window)
import { VariableSizeList } from 'react-window';
const rowHeights = new Array(1000)
.fill(true)
.map(() => 25 + Math.round(Math.random() * 50));
function DynamicList({ items }) {
return (
<VariableSizeList
height={600}
itemCount={items.length}
itemSize={index => rowHeights[index]}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ListItem item={items[index]} />
</div>
)}
</VariableSizeList>
);
}
3. Horizontal Lists
import { FixedSizeList } from 'react-window';
function HorizontalList({ items }) {
return (
<FixedSizeList
height={150}
itemCount={items.length}
itemSize={200}
layout="horizontal"
width={600}
>
{({ index, style }) => (
<div style={style}>
<ListItem item={items[index]} />
</div>
)}
</FixedSizeList>
);
}
Alternative Solutions
1. Pagination
function PaginatedList({ items }) {
const [page, setPage] = useState(1);
const itemsPerPage = 50;
const paginatedItems = items.slice(
(page - 1) * itemsPerPage,
page * itemsPerPage
);
return (
<div>
<ul>
{paginatedItems.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
<Pagination
currentPage={page}
totalItems={items.length}
itemsPerPage={itemsPerPage}
onPageChange={setPage}
/>
</div>
);
}
2. Infinite Loading
import { useInfiniteQuery } from 'react-query';
function InfiniteList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery(
'items',
({ pageParam = 0 }) => fetchItems(pageParam),
{
getNextPageParam: (lastPage) => lastPage.nextPage,
}
);
return (
<ul>
{data.pages.map(page => (
page.items.map(item => (
<ListItem key={item.id} item={item} />
))
))}
{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</ul>
);
}
Performance Optimization Tips
- Stable Keys: Ensure list items have unique, stable keys
- Memoize Components: Use
React.memo
for list items - Avoid Inline Styles/Functions: In item renderers
- Lazy Loading: For images/media within list items
- CSS Containment: Use
contain: strict
where possible
Common Pitfalls
- Not Virtualizing Large Lists:
// ❌ Renders all items regardless of visibility
{bigArray.map(item => <Item {...item} />)}
- Inefficient Item Components:
// ❌ Unmemoized with inline styles
const Item = ({ data }) => (
<div style={{ color: 'red' }}>{data.name}</div>
);
- Over-fetching Data:
// ❌ Loading all data at once
const [items, setItems] = useState([]);
useEffect(() => {
fetchAllItems().then(setItems); // Thousands of items
}, []);
Advanced Techniques
1. Custom Virtualization Hook
function useVirtualizer({ count, getItemHeight, containerRef }) {
const [scrollTop, setScrollTop] = useState(0);
const height = containerRef.current?.clientHeight || 0;
const virtualItems = [];
let startIndex = 0;
let offsetY = 0;
while (startIndex < count) {
const itemHeight = getItemHeight(startIndex);
if (offsetY + itemHeight > scrollTop) break;
offsetY += itemHeight;
startIndex++;
}
let endIndex = startIndex;
while (endIndex < count && offsetY < scrollTop + height) {
const itemHeight = getItemHeight(endIndex);
virtualItems.push({ index: endIndex, offset: offsetY });
offsetY += itemHeight;
endIndex++;
}
return { virtualItems, totalHeight: offsetY };
}
2. Grid Virtualization
import { FixedSizeGrid as Grid } from 'react-window';
function Matrix({ data }) {
return (
<Grid
columnCount={10}
columnWidth={100}
height={600}
rowCount={1000}
rowHeight={50}
width={1000}
>
{({ columnIndex, rowIndex, style }) => (
<div style={style}>
{data[rowIndex][columnIndex]}
</div>
)}
</Grid>
);
}
Key Takeaways
- Virtualization is Essential: For lists with 100+ items
- Choose the Right Solution: Based on your specific needs
- Measure Performance: Use React DevTools Profiler
- Progressive Loading: Consider infinite scroll or pagination
- Optimize Items: Each rendered item should be lightweight
Remember: The goal is to maintain a smooth user experience while displaying large datasets. Virtualization libraries like react-window
and react-virtualized
provide battle-tested solutions that can dramatically improve performance.