One way is by using a library like react-virtualized, which uses a technique called virtual rendering.

So how can we display these one thousand rows in an efficient way?

In less powerful devices or with more complex layouts, this could freeze the UI or even crash the browser.

In my case, the frames went from 60 to around 38 frames per second:

But if you’re using Chrome, follow these steps to do a quick test:

However, if you scroll through the list, you may not notice any lagging. I didn’t. After all, the app isn’t rendering something complex.

So many elements in the DOM can cause two problems:

It shouldn’t be a surprise to find one thousand div nodes in the DOM:

You can inspect the page using the Elements panel of your browser’s developer tools.

And run the app with npm start , you should see something like this:

Now, if you add some CSS styles to src/App.css :

Using the method renderRow() to create the layout of each row:

This way, the render() method can use the array like this:

The above code will generate an array of one thousand objects with the properties:

And let’s create an array of one thousand elements in the following way:

The placeholder text will be generated with the library lorem-ipsum , so cd into your app directory and install it:

Click here to check it out

This app is going to show a list of one thousand comments. Something like this:

You can find the complete source code of the examples used here in this GitHub repository .

You’ll also learn about two other helpful components. CellMeasurer , to dynamically measure the width and height of the rows, and ScrollSync , to synchronize scrolling between two or more virtualized components.

Then, you’ll learn how React Virtualized solves those problems and how to efficiently render the list of the first example using the List and Autosizer components.

First, you’ll see the problems with rendering a huge data set.

In this article, I’ll show you how to use react-virtualized to display a large amount of data efficiently.

And what if techniques like pagination or infinite scrolling are not an option (or maybe there are but you still have to show a lot of information)?

But what if you need to show thousands of rows at the same time?

A common requirement in web applications is displaying lists of data. Or tables with headers and scrolls. You have probably done it hundreds of times.

How does react-virtualized work?

The main concept behind virtual rendering is rendering only what is visible.

There are one thousand comments in the app, but it only shows around ten at any moment (the ones that fit on the screen), until you scroll to show more.

So it makes sense to load only the elements that are visible and unload them when they are not by replacing them with new ones.

React-virtualized implements virtual rendering with a set of components that basically work in the following way:

They calculate which items are visible inside the area where the list is displayed (the viewport).

They use a container ( div ) with relative positioning to absolute position the children elements inside of it by controlling its top, left, width and height style properties.

There are five main components:

Grid. It renders tabular data along the vertical and horizontal axes.

List. It renders a list of elements using a Grid component internally.

component internally. Table. It renders a table with a fixed header and vertically scrollable body content. It also uses a Grid component internally.

component internally. Masonry. It renders dynamically-sized, user-positioned cells with vertical scrolling support.

Collection. It renders arbitrarily positioned and overlapping data.

These components extend from React.PureComponent, which means that when comparing objects, it only compares their references, to increase performance. You can read more about this here.

On the other hand, react-virtualized also includes some HOC components:

ArrowKeyStepper. It decorates another component so it can respond to arrow-key events.

AutoSizer. It automatically adjusts the width and height of another component.

CellMeasurer. It automatically measures a cell’s contents by temporarily rendering it in a way that is not visible to the user.

ColumnSizer. It calculates column-widths for Grid cells.

InfiniteLoader. It manages the fetching of data as a user scrolls a List, Table, or Grid.

MultiGrid. It decorates a Grid component to add fixed columns and/or rows.

ScrollSync.It synchronizes scrolling between two or more components.

WindowScroller. It enables a Table or List component to be scrolled based on the window’s scroll positions.

Now let’s see how to use the List component to virtualize the one thousand comments example.

Virtualizing a list

First, in src/App.js , import the List component from react-virtualizer:

import { List } from "react-virtualized";

Now instead of rendering the list in this way:

<div className="list"> {this.list.map(this.renderRow)} </div>

Let’s use the List component to render the list in a virtualized way:

const listHeight = 600; const rowHeight = 50; const rowWidth = 800; //... <div className="list"> <List width={rowWidth} height={listHeight} rowHeight={rowHeight} rowRenderer={this.renderRow} rowCount={this.list.length} /> </div>

Notice two things.

First, the List component requires you to specify the width and height of the list. It also needs the height of the rows so it can calculate which rows are going to be visible.

The rowHeight property takes either a fixed row height or a function that returns the height of a row given its index.

Second, the component needs the number of rows (the list length) and a function to render each row. It doesn’t take the list directly.

For this reason, the implementation of the renderRow method needs to change.

This method won’t receive an object of the list as an argument anymore. Instead, the List component will pass it an object with the following properties:

index .The index of the row.

.The index of the row. isScrolling . Indicates if the List is currently being scrolled.

. Indicates if the is currently being scrolled. isVisible . Indicates if the row is visible on the list.

. Indicates if the row is visible on the list. key . A unique key for the row.

. A unique key for the row. parent . A reference to the parent List component.

. A reference to the parent component. style . The style object to be applied to the row to position it.

Now the renderRow method will look like this:

renderRow({ index, key, style }) { return ( <div key={key} style={style} className="row"> <div className="image"> <img src={this.list[index].image} alt="" /> </div> <div className="content"> <div>{this.list[index].name}</div> <div>{this.list[index].text}</div> </div> </div> ); }

Note how the index property is used to access the element of the list that corresponds to the row that is being rendered.

If you run the app, you’ll see something like this:

In my case, eight and a half rows are visible.

If we look at the elements of the page in the developer tools tab, you’ll see that now the rows are placed inside two additional div elements:

The outer div element (the one with the CSS class ReactVirtualized__GridReactVirtualized__List ) has the width and height specified in the component (800px and 600px, respectively), has a relative position and the value auto for overflow (to add scrollbars).

The inner div element (the one with the CSS class ReactVirtualized__Grid__innerScrollContainer ) has a max-width of 800px but a height of 50000px, the result of multiplying the number of rows (1000) by the height of each row (50). It also has a relative position but a hidden value for overflow.

All the rows are children of this div element, and this time, there are not one thousand elements.

However, there are not eight or nine elements either. There’s like ten more.

That’s because the List component renders additional elements to reduce the chance of flickering due to fast scrolling.

The number of additional elements is controlled with the property overscanRowCount. For example, if I set 3 as the value of this property:

<List width={rowWidth} height={listHeight} rowHeight={rowHeight} rowRenderer={this.renderRow} rowCount={this.list.length} overscanRowCount={3} />

The number of elements I’ll see in the Elements tab will be around twelve.

Anyway, if you repeat the frame rate test, this time you’ll see a constant rate of 59/60 fps:

Also, take a look at how the elements and their top style is updated dynamically:

The downside is that you have to specify the width and height of the list as well as the height of the row.

Luckily, you can use the AutoSizer and CellMeasurer components to solve this.

Let’s start with AutoSizer .

Autoresizing a virtualized list

Components like AutoSizer use a pattern named function as child components.

As the name implies, instead of passing a component as a child:

<AutoSizer> <List ... /> </AutoSizer>

You have to pass a function. In this case, one that receives the calculated width and height:

<AutoSizer> ({ width, height }) => { } </AutoSizer>

This way, the function will return the List component configured with the width and height:

<AutoSizer> ({ width, height }) => { return <List width={width} height={height} rowHeight={rowHeight} rowRenderer={this.renderRow} rowCount={this.list.length} overscanRowCount={3} /> } </AutoSizer>

The AutoSizer component will fill all of the available space of its parent so if you want to fill all the space after the header, in src/App.css , you can add the following line to the list class:

.list { ... height: calc(100vh - 210px) }

The vh unit corresponds to the height to the viewport (the browser window size), so 100vh is equivalent to 100% of the height of the viewport. 210px are subtracted because of the size of the header (200px) and the padding that the list class adds (10px).

Import the component if you haven’t already:

import { List, AutoSizer } from "react-virtualized";

And when you run the app, you should see something like this:

If you resize the window, the list height should adjust automatically:

Calculating the height of a row automatically

The app generates a short sentence that fits in one line, but if you change the settings of the lorem-ipsum generator to something like this:

this.list = Array(rowCount).fill().map((val, idx) => { return { //... text: loremIpsum({ count: 2, units: 'sentences', sentenceLowerBound: 10, sentenceUpperBound: 100 }) } });

Everything becomes a mess:

That’s because the height of each cell has a fixed value of 50. If you want to have dynamic height, you have to use the CellMeasurer component.

This component works in conjunction with CellMeasurerCache, which stores the measurements to avoid recalculate them all the time.

To use these components, first import them:

import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from "react-virtualized";

Next, in the constructor, create an instance of CellMeasurerCache :

class App extends Component { constructor() { ... this.cache = new CellMeasurerCache({ fixedWidth: true, defaultHeight: 100 }); } ... }

Since the width of the rows doesn’t need to be calculated, the fixedWidth property is set to true .

Unlike AutoSizer , CellMeasurer doesn’t take a function as a child, but the component you want to measure, so modify the method renderRow to use it in this way:

renderRow({ index, key, style, parent }) { return ( <CellMeasurer key={key} cache={this.cache} parent={parent} columnIndex={0} rowIndex={index}> <div style={style} className="row"> <div className="image"> <img src={this.list[index].image} alt="" /> </div> <div className="content"> <div>{this.list[index].name}</div> <div>{this.list[index].text}</div> </div> </div> </CellMeasurer> ); }

Notice the following about CellMeasuer :

This component is the one that is going to take the key to differentiate the elements.

It takes the cache configured before.

It takes the parent component ( List ) where it’s going to be rendered, so you also need this parameter.

Finally, you only need to modify the List component so it uses the cache and gets its height from that cache:

<AutoSizer> { ({ width, height }) => { return <List width={width} height={height} deferredMeasurementCache={this.cache} rowHeight={this.cache.rowHeight} rowRenderer={this.renderRow} rowCount={this.list.length} overscanRowCount={3} /> } } </AutoSizer>

Now, when you run the app, everything should look fine:

Syncing scrolling between two lists

Another useful component is ScrollSync .

For this example, you’ll need to return to the previous configuration that returns one short sentence:

text: loremIpsum({ count: 1, units: 'sentences', sentenceLowerBound: 4, sentenceUpperBound: 8 })

The reason is that you cannot share a CellMeausure cache between two components, so you cannot have dynamic heights for the two lists I’m going to show next like in the previous example. At least not in an easy way.

If you want to have dynamic heights for something similar to the example of this section, it’s better to use the MultiGrid component.

Moving on, import ScrollSync :

import { List, AutoSizer, ScrollSync } from "react-virtualized";

And in the render method, wrap the div element with the list class in a ScrollSync component like this:

<ScrollSync> {({ onScroll, scrollTop, scrollLeft }) => ( <div className="list"> <AutoSizer> { ({ width, height }) => { return ( <List width={width} height={height} rowHeight={rowHeight} onScroll={onScroll} rowRenderer={this.renderRow} rowCount={this.list.length} overscanRowCount={3} /> ) } } </AutoSizer> </div> ) } </ScrollSync>

ScrollSync also takes a function as a child to pass some parameters. Perhaps the ones that you’ll use most of the time are:

onScroll . A function that will trigger updates to the scroll parameters to update the other components, so it should be passed to at least one of the child components.

. A function that will trigger updates to the scroll parameters to update the other components, so it should be passed to at least one of the child components. scrollTop . The current scroll-top offset, updated by the onScroll function.

. The current scroll-top offset, updated by the function. scrollLeft . The current scroll-left offset, updated by the onScroll function.

If you put a span element to display the scrollTop and scrollLeft parameters:

... <div className="list"> <span>{scrollTop} - {scrollLeft}</span> <AutoSizer> ... </AutoSizer> </div>

And run the app, you should see how the scrollTop parameter is updated as you scroll the list:

As the list doesn’t have a horizontal scroll, the scrollLeft parameter doesn’t have a value.

Now, for this example, you’ll add another list that will show the ID of each comment and its scroll will be synchronized to the other list.

So let’s start by adding another render function for this new list:

renderColumn({ index, key, style }) { return ( <div key={key} style={style} className="row"> <div className="content"> <div>{this.list[index].id}</div> </div> </div> ); }

Next, in the AutoSizer component, disable the width calculation:

<AutoSizer disableWidth> { ({ height }) => { ... } } </AutoSizer>

You don’t need it anymore because you’ll set a fixed width to both lists and use absolute position to place them next to each other.

Something like this:

<div className="list"> <AutoSizer disableWidth> { ({ height }) => { return ( <div> <div style={{ position: 'absolute', top: 0, left: 0, }}> <List className="leftSide" width={50} height={height} rowHeight={rowHeight} scrollTop={scrollTop} rowRenderer={this.renderColumn} rowCount={this.list.length} overscanRowCount={3} /> </div> <div style={{ position: 'absolute', top: 0, left: 50, }}> <List width={800} height={height} rowHeight={rowHeight} onScroll={onScroll} rowRenderer={this.renderRow} rowCount={this.list.length} overscanRowCount={3} /> </div> </div> ) } } </AutoSizer> </div>

Notice that the scrollTop parameter is passed to the first list so its scroll can be controlled automatically, and the onScroll function is passed to the other list to update the scrollTop value.

The leftSide class of the first list just hides the scrolls (because you won’t be needing it):

.leftSide { overflow: hidden !important; }

Finally, if you run the app and scroll the right-side list, you’ll see how the other list is also scrolled:

Ensure your large lists render properly Debugging React applications can be difficult, especially when there is complex state or large components like lists. If you’re interested in monitoring and tracking Redux state for all of your users in production, try LogRocket. https://logrocket.com/signup/ LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores. Modernize how you debug your React apps – Start monitoring for free.

Conclusion

This article, I hope, showed you how to use react-virtualized to render a large list in an efficient way. It only covered the basics, but with this foundation, you should be able to use other components like Grid and Collection.

Of course, there are other libraries built for the same purpose, but react-virtualized has a lot of functionality and it’s well maintained. Plus, there is a Gitter chat and a StackOverflow tag for asking questions.

Remember that you can find all the examples in this GitHub repository.