Ever wonder how to render a huge number of records in a react component and not killing your browser. For example, rendering a list with thousands of items or a Datagrid with a high density of columns and rows.
One way of solving this problem is to use a technique called "virtual rendering".The basic idea of "VR" is to only render what the user sees, keeping the number of rendered objects to the minimum.
A typical use of "VR" with React will be the implementation of a list component that contains thousands of elements. In this post, I going to show how easy is to implement a React Component that uses the "virtual render" technique. Here is the complete example in jsfiddle.
To begin the first thing that we need is a viewPort area. This area is the one that is going to be responsible to contain the visible items of the list and has a scrollbar that enables the user to navigate through all the items.
The code for this example can be found at Github.
I have also made a react timeline component using the same technic, the repo link is here or can be installed using npm install react-gantt-timeline.
To accomplish this in HTML we are going to create a div called "viewport" and inside we are going to add another div which height is going to be h=height of a row * number of rows. So now the viewport div is going to be able to scroll with the right scroll length, the last thing to do is add to our the viewport css the property overflow-y: scroll.
So lets put this together in a react component and add a simple class for render rows that we are going to call "Item". The code and the css will look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Item extends React.Component{
constructor(props){
super(props);}
render(){
return (
<div className="item" style={{top:this.props.top}}>
{this.props.label}
</div>)
}
}
class Vlist extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div ref="viewPort" className="viewPort">
<div className="itemContainer">
</div>
</div>)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
viewPort {
position: relative;
width: 510px;
height: 300px;
border: solid 1px;
overflow-y: scroll;
}
.itemContainer {
position: absolute;
width: 500px;
background-color: azure;
}
.item {
position: absolute;
background-color: beige;
border: solid 1px;
width: 500px;
text-align:center;
}
So now let's create some data and initialize our component state. We are going to create some important variables: 1. The height of a row (itemheight)that we are going to pass as a property 2. With itemheight we can calculate the total height of the itemcontainer div (h=itemheight * numRows) and we are going to create a variable call (containerStyle) to use as an inline style. 3.The number of visible items in the viewPort (numVisibleItems)=viewPort.height/itemheight 4. We are going to initialize the state that will contain the index of the rows to render.
Now that we have all set up we also can create a renderRows method that using start and end can render the visible rows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const data = []
function createData(){
for (let i=0;i<10000;i++){
data.push({name:"Row"+i);
}
}
createData();
//Item class to render each of the list rows
class Item extends React.Component{
constructor(props){
super(props);
}
//Adding Style for dynamic rowheight
render(){
return (
<div className="item" style={{top:this.props.top,height:this.props.itemheight}}>
{this.props.label}
</div>)
}
}
class Vlist extends React.Component{
constructor(props){
super(props);
//Calculating the number of visible items
this.numVisibleItems=Math.trunc(300 / this.props.itemheight);
//Initialising index of visible items
this.state={
start:0,
end:this.numVisibleItems
}
//Initialise style for the itemContainer
this.containerStyle={height:data.length * this.props.itemheight}
}
//Rendering visble Rows
renderRows(){
let result=[];
for (let i=this.state.start;i<this.state.end+1;i++){
let item=data[i];
result.push(<Item key={i} label={item.name} top={i*this.props.itemheight} itemheight={this.props.itemheight} />);
}
return result;
}
render(){
return (
<div ref="viewPort" className="viewPort" >
<div className="itemContainer" style={this.containerStyle}>
{this.renderRows()}
</div>
</div>)
}
}
The renderRow method is where all the magic happens, here we only render the visible elements and we position each item to his right top matching with the position of the scroll bar of the viewport div.
That is cool now we have a list that can display the first n visible items with a scrollbar. The last thing to do is to add a listener to the scrollbar so as it moves we can change the state properties start and end index. Changing the state will trigger an update and we will see the right rows rendering given a new scroll position. New items will be created and the not visible items will be destroyed.
This diagram shows what is happening as we scroll:
Now we can take a look at the end result. The code is super fast with almost no impact in the CPU when scrolling.
Test our Latest app. It is called K8 Studio and is a cross-platform IDE to manage Kubernetes Clusters visually.