React-Virtualized: Why, When and How you should use it
Cyril Gaunet5 min read
Why
What is a Virtual DOM ?
The virtual DOM (VDOM) is a programming concept where an ideal, or “virtual”, representation of a UI is kept in memory.
Then, it is synced with the “real” DOM by a library such as ReactDOM. This process is called reconciliation.
Performance and windowing
You might know that React uses this virtual DOM. Thus, it is only when React renders elements that the user will have them into his/her HTML DOM.
Sometimes you might want to display a lot of html elements, like for grids, lists, calendars, dropdowns etc and the user will often complain about performance.
Hence, a good way to display a lot of information is to ‘window’ it. The idea is to create only elements the user can see. An example is the Kindle vs Book. While the book is a heavy object because it ‘renders’ all the pages, the Kindle only display what the user can see.
React-Virtualized
That is how Brian Vaughn came up with the idea of creating React-Virtualized.
It is an open-source library which provides you many components in order to window some of your application List, Grid etc
As a developer, you do not want to reinvent the wheel. React-virtualized is a stable and maintained library. Its community is large and as it is open-source, many modules and extensions are already available in order to window a maximum of elements.
Furthermore, it offers lots of functionalities and customization that you would not even think about.
We will discuss about it later, but before, let’s see when to use React-virtualized.
When
When thinking about performance, lots of actions can be taken, but React official website already got a complete article to be read. In consequence, if you face a performance problem, be sure you have already done all of these before to start to window your application (but stay pragmatic).
How
Getting into it
Ok, now that you’re convinced, let’s go throught the real part.
You can begin by following instructions for installing the right package via npm and look at simple examples here : React virtualized github. However, I’m going to show you a complex example so you can use React-Virtualized in an advanced way.
React-Virtualized 101
To render a windowed list, no need for digging one hour a complex documentation, React-Virtualized is very simple to use.
Firstly, you use the List component from the library, then, the few important props are the next one:
- width: the width of the List
- height: the height of the List
- rowCount: the number of elements you will display
- rowHeight: the height of each row you will display
- rowRenderer: a callback method to define yourself depending on what you want to do with your data. This method is the core of your list, it is here that you define what will be rendered thanks to your data.
The example
import React from 'react';
import { List } from 'react-virtualized';
// List data as an array of strings
const list = [
'Easy windowing1',
'Easy windowing2',
// And so on...
];
function rowRenderer({
key, // Unique key within array of rows
index, // Index of row within collection
isScrolling, // Used for performance
isVisible, // Used for performancee
style, // Style object to be applied to row (to position it)
}) {
return (
<div key={key} style={style}>
{list[index]}
</div>
);
}
// Render your list
const ListExample = () => (
<List width={300} height={300} rowCount={list.length} rowHeight={20} rowRenderer={rowRenderer} />
);
Click here to see a demo
A more complex virtualized list:
Display a virtualized list might be easy, but you might have a complicated behaviour to implement.
In this advanced example, we will:
- Use the AutoSizer HOC to automatically calculate the size the List will fill
- Be able to display row with a dynamic height using the CellMeasurer
- Be able to use the CellMeasurer even if the data are not static
This advanced example goes through 4 steps:
- Instantiate the AutoSizer and List component
- See how the CellMeasurer and the CellMeasurerCache work
- Learn how we use them in the rowRenderer
- Go further with using these on a list that does not contain a stable number of elements
The example
Let’s look first at how we render the list:
import {
AutoSizer,
List,
CellMeasurer,
CellMeasurerCache,
} from 'react-virtualized';
...
<AutoSizer>
{({ height, width }) => (
<List
width={width}
height={height}
rowGetter={({ index }) => rows[index]}
rowCount={1000}
rowHeight={40}
rowRenderer={this.rowRenderer}
headerHeight={20}
/>
)}
</AutoSizer>
It is very simple:
- We wrap the list with the AutoSizer HOC
- It uses the CellMeasurerCache to know the height of each row and the rowRenderer to render the elements.
How it works :
First, you instantiate a new CellMeasurerCache that will contain all the calculated heights :
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({ //Define a CellMeasurerCache --> Put the height and width you think are the best
defaultHeight: 80,
minHeight: 50,
fixedWidth: true,
});
}
Then, you use the CellMeasurer in the rowRenderer method:
rowRenderer = ({
key, // Unique key within array of rows
index, // Index of row within collection
parent,
isScrolling, // The List is currently being scrolled --> Important if you need some perf adjustment
isVisible, // This row is visible within the List (eg it is not an overscanned row)
style, // Style object to be applied to row (to position it)
}) => (
<CellMeasurer
cache={this.cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<div
className="Row"
key={key}
style={{
...style,
display: 'flex',
}}
>
<span style={{ width: 400 }}>{rows[index].name}</span>
<span style={{ width: 100 }}>{rows[index].age}</span>
</div>
</CellMeasurer>
);
Pitfall:
Finally, we obtain a nice windowed list, ready to be deployed and used…
Unless your application contain filters or some data added dynamically.
Actually, when I implemented this, after using some filters, some blank spaces were staying in the list.
It is a performance consideration due to the fact we use a cache, but it is a good compromise unless you have many rows and many columns in a Grid (as we display a list, we only have 1 column).
Consequently, I managed to fix this issue by clearing the cache every time my list had its data reloaded:
componentWillReceiveProps() { //Really important !!
this.cache.clearAll(); //Clear the cache if row heights are recompute to be sure there are no "blank spaces" (some row are erased)
this.virtualizedList && this.virtualizedList.recomputeRowHeights(); //We need to recompute the heights
}
A big thanks to Brian Vaughn for this amazing library