![]()
Optimizing Large List Rendering in React
A common performance issue in React applications occurs when rendering large lists without virtualization, causing sluggish UI and poor user experience. Here’s how to properly handle large datasets.
The Problem (Unoptimized List Rendering)
// ❌ Poor performance with large datasets
function BigList({ items }) {
return (
<div>
{items.map(item => (
<ListItem key={item.id} item={item} />
))}
</div>
);
}
// Usage with 10,000 items - will freeze the UI
<BigList items={hugeArray} />
Why this is problematic:
- Renders all items at once (DOM overload)
- Causes slow initial render
- Makes scrolling janky
- Wastes memory with off-screen elements
- Triggers excessive re-renders
Correct Solutions
1. Using React-Window (Recommended)
import { FixedSizeList as List } from 'react-window';
function BigList({ items }) {
return (
<List
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ListItem item={items[index]} />
</div>
)}
</List>
);
}
2. Using React-Virtualized
import { List } from 'react-virtualized';
function BigList({ items }) {
return (
<List
width={300}
height={600}
rowCount={items.length}
rowHeight={50}
rowRenderer={({ index, key, style }) => (
<div key={key} style={style}>
<ListItem item={items[index]} />
</div>
)}
/>
);
}
3. Pagination Approach
function PaginatedList({ items }) {
const [page, setPage] = useState(0);
const itemsPerPage = 50;
const pageCount = Math.ceil(items.length / itemsPerPage);
const visibleItems = items.slice(
page * itemsPerPage,
(page + 1) * itemsPerPage
);
return (
<div>
{visibleItems.map(item => (
<ListItem key={item.id} item={item} />
))}
<Pagination
pageCount={pageCount}
onPageChange={({ selected }) => setPage(selected)}
/>
</div>
);
}
Key Optimization Techniques
- Windowing/Virtualization:
- Only renders visible items
- Recycles DOM nodes
- Maintains scroll position
- Pagination:
- Splits data into chunks
- Simple to implement
- Good for known-length data
- Infinite Loading:
- Loads more items as user scrolls
- Works well with APIs
- Requires scroll detection
Common Mistakes to Avoid
- Using array indexes as keys:
{items.map((item, index) => (
<ListItem key={index} /> // ❌ Bad for reordering
))}
- Complex item components:
- Heavy renders per item compound performance issues
- No memoization:
// ❌ Re-renders all items when parent updates
const ListItem = ({ item }) => <div>{item.name}</div>;
- Ignoring overscan:
- Blank areas during fast scrolling
Best Practices
- Always use stable keys from your data
- Memoize list items:
const MemoizedListItem = React.memo(ListItem);
- Implement overscan for smooth scrolling:
<List overscanCount={5} {...otherProps} />
- Measure performance with React DevTools
- Consider intersection observers for custom solutions
Advanced Patterns
1. Dynamic Item Sizes
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>
);
}
2. Infinite Loading with Virtualization
import { useInfiniteLoader, List } from 'react-virtualized';
function InfiniteList({ items, loadMoreRows }) {
const loadMore = useInfiniteLoader({
isRowLoaded: ({ index }) => !!items[index],
loadMoreRows,
rowCount: items.length + 1, // +1 for loading indicator
});
return (
<List
width={300}
height={600}
rowCount={items.length}
rowHeight={50}
onRowsRendered={loadMore}
rowRenderer={({ index, key, style }) => (
<div key={key} style={style}>
{items[index] ? (
<ListItem item={items[index]} />
) : (
<LoadingIndicator />
)}
</div>
)}
/>
);
}
3. Custom Virtualization Hook
function useVirtualizer({ size, parentRef, estimateSize }) {
const [scrollTop, setScrollTop] = useState(0);
const height = parentRef.current?.clientHeight || 0;
const virtualItems = useMemo(() => {
const rangeStart = scrollTop;
const rangeEnd = scrollTop + height;
let startIndex = 0;
let endIndex = 0;
let totalSize = 0;
// Calculate visible range
for (let i = 0; i < size; i++) {
const itemSize = estimateSize(i);
if (totalSize <= rangeEnd) endIndex = i;
if (totalSize < rangeStart) startIndex = i;
totalSize += itemSize;
}
return { startIndex, endIndex, totalSize };
}, [scrollTop, height, size, estimateSize]);
const onScroll = useCallback(e => {
setScrollTop(e.currentTarget.scrollTop);
}, []);
return { virtualItems, onScroll };
}
Remember: For lists with more than 100-200 items, always consider virtualization. The exact threshold depends on:
- Complexity of your list items
- Target device capabilities
- User experience requirements
Popular virtualization libraries include:
- react-window (recommended)
- react-virtualized
- tanstack-virtual (framework agnostic)
