Boosting Frontend Rendering Performance with react-virtualized-list and Intersection Observer
This article explains how the react-virtualized-list library combined with the Intersection Observer API can dramatically improve rendering performance for large data sets, achieving up to 95% faster page loads and smoother infinite scrolling on both PC and mobile browsers.
Boosting Frontend Rendering Performance with react-virtualized-list and Intersection Observer
Introduction: Rendering massive data sets on the front end often leads to performance bottlenecks. This article explores how the react-virtualized-list library, together with the Intersection Observer API , can increase rendering speed by up to 50% and overall page load speed by 95%.
Background
Our monitoring system recently suffered from loading lag and white‑screen issues when displaying large lists. The core requirement was a dynamic, auto‑refreshing list, which led us to adopt react-virtualized-list . If you are looking for ways to overcome large‑data rendering bottlenecks, this article provides practical solutions.
What Is Virtualization?
Virtualization renders only the items visible in the viewport instead of the entire data set, drastically reducing DOM nodes and improving performance. It loads and unloads elements as the user scrolls, keeping the UI fluid.
Below is how react-virtualized-list handles virtualization:
Real DOM comparison:
react-virtualized-list Overview
react-virtualized-list is a high‑performance React component for displaying large data sets. It works on both PC and mobile platforms, offering lazy loading, infinite scrolling, and is ideal for chat logs, product catalogs, etc.
Core Features 🔥🔥
High performance: Renders only items inside the viewport, dramatically reducing DOM nodes.
Lazy loading: Dynamically loads data to avoid massive one‑time loads.
Infinite scroll: Allows continuous scrolling to load more content.
Custom rendering: Flexible API for custom item rendering.
Viewport refresh: Auto‑refreshes visible content for real‑time data.
TS/JS support: Works with TypeScript and JavaScript projects.
Installation
Install via npm or yarn:
npm install react-virtualized-list
# or
yarn add react-virtualized-listBasic Usage
Simple example of an infinite‑scroll virtualized list:
import React, { useState, useEffect } from 'react';
import VirtualizedList from 'react-virtualized-list';
import './style/common.css';
const InfiniteScrollList = () => {
const [items, setItems] = useState([]);
const [hasMore, setHasMore] = useState(true);
const loadMoreItems = () => {
// Simulate API call with 1‑second delay
setTimeout(() => {
const newItems = Array.from({ length: 20 }, (_, index) => ({
id: items.length + index,
text: `Item ${items.length + index}`
}));
setItems(prev => [...prev, ...newItems]);
setHasMore(newItems.length > 0);
}, 1000);
};
useEffect(() => { loadMoreItems(); }, []);
const renderItem = item =>
{item.text}
;
return (
Loading...
}
endMessage={
No more items
}
/>
);
};
export default InfiniteScrollList; /* ./style/common.css */
.content {
width: 350px;
padding: 16px;
border: 1px solid red;
margin-top: 10vh;
}
.item-class {
height: 50px;
border: 1px solid blue;
margin: 0 0 10px;
padding: 10px;
background-color: #f0f0f0;
}Using onLoadMore and hasMore enables automatic loading when the user reaches the bottom of the list.
Advanced Usage
1. Dynamic Data Loading
Combine react-virtualized-list with asynchronous data fetching for even better performance:
import React, { useState, useEffect } from 'react';
import VirtualizedList from 'react-virtualized-list';
import './style/common.css';
const fetchProductData = async product => {
return new Promise(resolve => {
setTimeout(() => {
resolve({
description: `Description for ${product.name}`,
imageUrl: `https://via.placeholder.com/150?text=Product+${product.id}`
});
}, 500);
});
};
const fetchProducts = async page => {
return new Promise(resolve => {
setTimeout(() => {
const products = Array.from({ length: 10 }, (_, i) => ({
id: page * 10 + i,
name: `Product ${page * 10 + i}`
}));
resolve(products);
}, 500);
});
};
const DynamicInfiniteList = () => {
const [products, setProducts] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(0);
const loadMoreProducts = async () => {
const newProducts = await fetchProducts(page);
setProducts(prev => [...prev, ...newProducts]);
setPage(prev => prev + 1);
if (newProducts.length < 10) setHasMore(false);
};
useEffect(() => { loadMoreProducts(); }, []);
return (
(
{product.name}
{data ? data.description : 'Loading...'}
{data &&
}
)}
itemClassName='item-class-dynamic'
fetchItemData={fetchProductData}
onLoadMore={loadMoreProducts}
hasMore={hasMore}
containerHeight='500px'
loader='Loading more products...'
endMessage='No more products'
/>
);
};
export default DynamicInfiniteList; /* ./style/common.css */
.content { width: 350px; padding: 16px; border: 1px solid red; margin-top: 10vh; }
.item-class-dynamic { height: 300px; padding: 20px; border-bottom: 1px solid #eee; }Note: The onLoadMore hook simulates product list scrolling and, together with fetchItemData , loads item details dynamically—especially useful when the backend cannot return the entire data set at once.
2. Custom Rendering
Customize each list item’s appearance and content:
import React from 'react';
import VirtualizedList from 'react-virtualized-list';
const data = Array.from({ length: 1000 }, (_, index) => ({
title: `Item ${index}`,
index,
description: `This is the description for item ${index}.`
}));
const ListItem = ({ item, style }) => (
{item.title}
{item.description}
);
const itemStyle = {
height: '100px',
border: '1px solid blue',
margin: '0 0 10px',
padding: '10px',
backgroundColor: '#f0f0f0'
};
const MyVirtualizedList = () => (
(
)}
containerHeight='80vh'
/>
);
export default MyVirtualizedList;Implementation Principles (Core Highlights)
Large‑scale web apps often need to display massive lists (e.g., e‑commerce product catalogs). Traditional rendering creates all DOM nodes at once, causing slow loads and janky scrolling.
Virtualized lists solve this by rendering only visible items. The react-virtualized-list library achieves this through three main mechanisms:
1. Viewport Monitoring with Intersection Observer API
Traditional scroll listeners calculate each element’s position on every scroll event, which is inefficient. Using Intersection Observer, we can efficiently detect visibility changes without heavy calculations.
// Traditional scroll monitoring (inefficient)
const elements = document.querySelectorAll('.target-element');
window.addEventListener('scroll', () => {
elements.forEach(element => {
const rect = element.getBoundingClientRect();
if (rect.top >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)) {
console.log(`${element} is visible.`);
}
});
});
// Intersection Observer version (efficient)
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(`${entry.target} is visible.`);
}
});
});
const elements = document.querySelectorAll('.target-element');
elements.forEach(element => observer.observe(element));A custom React hook useIntersectionObserver wraps this API for easy reuse.
2. Render Only Visible Items
By tracking visible indices, the library renders only items within the calculated range, adding a small buffer to avoid flicker.
const [visibleItems, setVisibleItems] = useState
>(new Set());
const handleVisibilityChange = useCallback((isVisible, entry) => {
const index = parseInt(entry.target.getAttribute('data-index')!, 10);
setVisibleItems(prev => {
const newSet = new Set(prev);
isVisible ? newSet.add(index) : newSet.delete(index);
return newSet;
});
}, []);
const { observe, unobserve } = useIntersectionObserver(containerRef.current, handleVisibilityChange);3. Dynamic Load/Unload to Minimize Memory
When an item enters the viewport it is loaded; when it leaves, it is unmounted, keeping memory usage low.
const visibleRange = useMemo(() => {
const sorted = [...visibleItems].sort((a, b) => a - b);
const first = sorted[0] || 0;
const last = sorted[sorted.length - 1] || 0;
return [Math.max(0, first - BUFFER_SIZE), Math.min(listData.length - 1, last + BUFFER_SIZE)];
}, [visibleItems, listData.length]);Performance Comparison (50% Faster Rendering)
Method
Initial Render Time
Scroll Performance
Memory Usage
Traditional scroll listener
300 ms
Low
High
Intersection Observer API
150 ms
High
Low
Initial Render Time: Intersection Observer halves the initial render time because only visible items are rendered.
Scroll Performance: Traditional scroll handling suffers from frequent events; Intersection Observer leverages browser optimizations for smoother scrolling.
Memory Usage: Rendering only visible elements reduces memory consumption.
Performance Test Code
Measuring both approaches with console.time :
// Measure traditional scroll listener
console.time('Scroll');
window.addEventListener('scroll', () => {
const elements = document.querySelectorAll('.target-element');
elements.forEach(element => {
const rect = element.getBoundingClientRect();
if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
// simulate render logic
}
});
});
console.timeEnd('Scroll');
// Measure Intersection Observer
console.time('IntersectionObserver');
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// simulate render logic
}
});
});
const elements = document.querySelectorAll('.target-element');
elements.forEach(el => observer.observe(el));
console.timeEnd('IntersectionObserver');Result Summary
Traditional scroll listeners cause high CPU usage and complex calculations, especially with large data sets. Intersection Observer offers lower overhead, asynchronous callbacks, and broad applicability (lazy loading, infinite scroll, ad viewability, animations, etc.).
Project Outcome (95% Faster Load)
After optimization, page load time dropped from 15 seconds to 0.75 seconds—a 95% improvement.
Metric
Before
After
Improvement
Total Time
15000 ms (15 s)
750 ms
95% faster
Conclusion
Using react-virtualized-list together with the Intersection Observer API dramatically improves front‑end rendering performance for large data sets, boosting page load speed by up to 95% and delivering a smoother user experience on both desktop and mobile platforms.
Feel free to try this approach for your own large‑list rendering challenges.
References
Intersection Observer API documentation
react-virtualized-list GitHub repository
Detailed articles on Intersection Observer usage
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.