Efficiently Rendering a Large Number of Records in a React Component
As web applications become more complex, handling a vast amount of data efficiently is a typical challenge developers face. Rendering many records in a React component can often lead to performance issues and browser crashes. Fortunately, React offers a solution through virtual rendering techniques and viewport detection. This guide will explore the best practices for implementing this approach to ensure smooth user experiences and prevent your browser from slowing down or crashing.
Understanding the Problem
When dealing with a large dataset in a React component, the traditional approach of rendering all records simultaneously can overwhelm the browser’s rendering engine and lead to a sluggish user interface. This is especially true when dealing with hundreds or thousands of records, as the browser has to process and render each element, consuming both memory and processing power.
Introducing Virtual Rendering
Virtual rendering, also known as “lazy loading,” is a technique that optimises the rendering process by only rendering the items that are currently visible within the user’s viewport. Instead of rendering all the records at once, virtual rendering dynamically renders and updates only the elements within the screen’s visible area. This significantly reduces the strain on the browser and enhances the application’s performance.
First, we need to create a viewPort div to accomplish this in HTML. This div acts as a container for the items visible in the list. It also includes a scrollbar, allowing users to scroll through everything.
Then, inside this div, we will add another div element to act as an item container with a height equal to the size of a single row multiplied by the total number of rows. This will enable the viewport div to scroll with the appropriate scroll length.
Finally, we must apply the CSS property “overflow-y: scroll” to the viewport div to enable scrolling functionality.
So, let’s get into the code. In this article, we will create a react virtual rendering component that can be used on any project.
Okay, let’s create the component for the Viewport and the Item;
import { useRef } from 'react';
import './index.css';
const Item = ({top, itemHeight, children}) => {
return (
<div className="item" style={{ top: top, height: itemHeight }}>
{children}
</div>
);
};
const Viewport = (props) => {
const viewPortRef = useRef(null);
return (
<div ref={viewPortRef} className="viewPort"><div className="itemContainer"></div>
</div>
);
};
export default Viewport;
and here is the index.css (this is just a basic; you can change it to fit your needs later) :
.viewPort {
position: relative;
width: 100%;
height: 300px;
border: solid 1px;
overflow-y: scroll;
overflow-x: hidden;
}
.itemContainer {
position: absolute;
width: 100%;
background-color: azure;
}
.item {
position: absolute;
background-color: beige;
border: solid 1px;
width: 100%;
text-align: center;
}
Now, we are going to create some crucial variables for our Viewport component:
1️⃣ The height for each row of data (itemHeight) props we will pass whenever we use this virtualised viewport component.
2️⃣ With itemHeight, we can calculate the total height of the item container div (h=itemheight * numRows), and we are going to create a variable call (containerStyle) to use as an inline style that will update the height of the item container in CSS.
3️⃣ numVisibleItems to use as the number of visible items in the viewPort (numVisibleItems)=viewPort.height/itemHeight (this time, we will set the viewport visible height to 300).
4️⃣The visible range state will be used to render the items into the item container, where the default start is 0, and the end is the value of numVisibleItems.
And we need to have a render function to render our items.
So, our viewport component code will look like this:
const Viewport = ({ itemHeight, data }) => {
const viewPortRef = useRef(null);
const viewportHeight = 300;
const numVisibleItems = Math.trunc(viewportHeight / itemHeight);
const [visibleRange, setVisibleRange] = useState({ start: 0, end: numVisibleItems });
const containerStyle = { height: data.length * itemHeight };
const renderRows = () => {
let result = [];
for (let index = visibleRange.start; index < visibleRange.end + 1; index++) {
result.push(<Item key={index} top={index * itemHeight} itemheight={itemHeight}>{data[index]}</Item>);
}
return result;
};
return (
<div ref={viewPortRef} className="viewPort">
<div className="itemContainer" style={containerStyle}>
{renderRows()}
</div>
</div>
);
};
The renderRows method is where the magic happens. We only render the currently visible elements and position each item in the correct location based on the scroll position of the viewport div.
Now, we have a viewport component that can display the first n visible items with a scroll bar. The final step is to add a listener to the scrollbar so that as it moves, we can update the state properties’ visibleRange start and end index.
Let’s create a new function in our viewport component:
const scrollPos = () => {
let currentIndx = Math.trunc(viewPortRef.current.scrollTop / itemHeight);
currentIndx =
currentIndx - numVisibleItems >= data.length
? currentIndx - numVisibleItems
: currentIndx;
setVisibleRange({
start: currentIndx,
end:
currentIndx + numVisibleItems >= data.length
? data.length - 1
: currentIndx + numVisibleItems,
});
};
Changing the state will trigger a re-render, and we will see the appropriate rows rendered based on the new scroll position. New items will be created, and the non-visible items will be removed; how cool is that?
So, our final viewport component will look like this:
import { useRef, useState } from "react";
import "./index.css";
const Item = ({ top, itemHeight, children }) => {
return (
<div className="item" style={{ top: top, height: itemHeight }}>
{children}
</div>
);
};
const Viewport = ({ itemHeight, data }) => {
const viewPortRef = useRef(null);
const viewportHeight = 300;
const numVisibleItems = Math.trunc(viewportHeight / itemHeight);
const [visibleRange, setVisibleRange] = useState({
start: 0,
end: numVisibleItems,
});
const containerStyle = {
height: data.length * itemHeight,
};
const renderRows = () => {
let result = [];
for (
let index = visibleRange.start;
index < visibleRange.end + 1;
index++
) {
result.push(
<Item key={index} top={index * itemHeight} itemheight={itemHeight}>
{data[index]}
</Item>
);
}
return result;
};
const scrollPos = () => {
let currentIndex = Math.trunc(viewPortRef.current.scrollTop / itemHeight);
currentIndex =
currentIndex - numVisibleItems >= data.length
? currentIndex - numVisibleItems
: currentIndex;
setVisibleRange({
start: currentIndex,
end:
currentIndex + numVisibleItems >= data.length
? data.length - 1
: currentIndex + numVisibleItems,
});
};
return (
<div ref={viewPortRef} className="viewPort" onScroll={scrollPos}>
<div className="itemContainer" style={containerStyle}>
{renderRows()}
</div>
</div>
);
};
export default Viewport;
Congratulations!
You just completed creating a virtual rendering viewport component. Here is an example of an app that uses this component:
const dummyData = Array.from({ length: 1000000 }, (_, i) => `Row ${i}`);
//we will render 1000000 rows of "Row N" text
const App = () =>
<Viewport itemHeight={50} data={dummyData} />;
//this example each row we set to 50px height
export default App;
You can see the result here. As you can see, the code is super fast, with almost no impact on the CPU when scrolling.
Using React Virtualised Libraries
Several popular libraries, such as `react-window` and `react-virtualized`, can simplify the process. These libraries provide components like `List`, `Grid`, and `Table`, which utilise virtual rendering techniques to efficiently handle large datasets.
Using react-window
1️⃣ Installation: Install the react-window library using npm or yarn.
npm install react-window.
2️⃣ Integration: Import the necessary components from `react-window` and create a virtualised list.
import { FixedSizeList } from 'react-window';
function VirtualisedListComponent({ data }) {
return (
<FixedSizeList
height={400} // Set the height of the visible area
itemCount={data.length}
itemSize={60} // Set the height of each individual item
width={300} // Set the width of the visible area
>
{({ index, style }) => (
<div style={style}>{data[index]}</div>
)}
</FixedSizeList>
);
}
Using `react-virtualized`
1️⃣ Installation: Install the react-virtualized library using npm or yarn.
2️⃣ Integration: Import the required components and create a virtualised list.
import { List } from 'react-virtualized';
function VirtualisedListComponent({ data }) {
return (
<List
height={400}
rowCount={data.length}
rowHeight={60}
rowRenderer={({ index, key, style }) => (
<div key={key} style={style}>
{data[index]}
</div>
)}
width={300}
/>
);
}
Conclusion
Efficiently rendering many records in a React component is a fundamental challenge for developers. By implementing virtual rendering techniques and viewport detection, you can significantly improve the performance of your application and prevent browser slowdowns or crashes.
Utilising libraries like `react-window` or `react-virtualized` simplifies the implementation process, allowing you to focus on delivering a seamless user experience even when dealing with massive datasets. Combine virtual rendering with other performance optimisation strategies for the best results.
At Upscalix, we always listen to our clients’ requirements and build a solution that suits their needs. Our team consists of the best talent in the industry with expertise in frontend, backend, and mobile app development.
Contact us here to build your digital solution now! Our team will listen to your requirements and suggest the best solution for your business.
This article written by Upscalix’s fullstack developer, Dody.