How To Improve Rendering Performance in a 1,000-Item React List
Imagine you are building some kind of application with React that requires you to show a long list of items. Say, 1,000 items. What are your options? Just render the whole thing at once?
Today, my goal is to show you the problem so that you understand what we are solving and then present a solution.
Let's get started!
Create the Project
At this point, all of you should know how to create a new React application from the command line using create-react-app
. Just go to your terminal and run the following command to get going with a brand new React application:
npx create-react-app long-list-app
Let’s Render 1,000 Items
OK, let me first show you the problem. Have a look at the following component:
import React, {useEffect, useState} from 'react';
const generateNames = (count) => {
const temp = [];
for(let i=0;i<=count;i++) temp.push(`Test Name- ${i}`)
return temp;
}
export const LongList = () => {
const [names , setNames ] = useState([])
useEffect(() => {
setNames(generateNames(1000));
},[])
return <> {names.map(name => <ListItem name={name}/>)} </>
}
const ListItem = ({name}) => {
console.log(`rendered ${name}`)
return <div> Name is: {name} </div>
}
In this component, we are generating 1,000 names and rendering them inside a list. Now let’s have a look in the browser:
Although our window is capable of showing six items, from the console, we can see that 1,000 items are being rendered in the background!
This inefficient rendering can make your application really slow.
Now Show Me the Code!
We will use an awesome library named react-virtualized. The concept is really simple and elegant: Don’t render stuff you don’t see on the screen!
First, install the dependency:
yarn add react-virtualized
What this library does is export some wrapper components to wrap around your list. You provide the height, width, and a function to render, and the library handles the rest.
Let's update our code to use the List
component exported from the library:
import React, {useEffect, useState} from 'react';
import {List} from 'react-virtualized';
export const LongList = () => {
// SAME AS BEFORE
const renderRow = ({ index, key, style }) => {
return <ListItem style={style} key={key} name={names[index]}/>
}
return <List
width={800}
height={900}
rowHeight={30}
rowRenderer={renderRow}
rowCount={names.length}
/>
}
We have changed the rendering process with the List
component.
Notice one thing here: We passed some extra props. width
, height
, rowHeight
, and rowCount
are self-explanatory, while rowRender
is a render function for each row item.
Now let's see the result:
Now we can see that there are 39 items on the screen and exactly 39 items are rendered now. No unnecessary rendering!
Variable-Sized Screen
You have likely noticed that we are giving the height and width of the container as a constant. But what if the screen of the users is of a different size. You just can’t say, “But it works on my machine!”
react-virtualized has already solved this problem. It has another component named AutoSizer
that helps to find the height and width of the container of the list dynamically. It follows a render-props
pattern.
Let’s update our code like so:
import React, {useEffect, useState} from 'react';
import {List , AutoSizer} from 'react-virtualized';
export const LongList = () => {
// SAME AS BEFORE
return <div style={{height:"100vh"}}>
<AutoSizer>
{({ width, height }) => {
return <List
width={width}
height={height}
rowHeight={30}
rowRenderer={renderRow}
rowCount={names.length}
overscanRowCount={3} />
}}
</AutoSizer>
</div>
}
Now the height and width of the screen are automatically updated based on the user's screen size.
Variable-Sized Row
So we solved the height and width issue of the container. But what about the individual row items? What if their size varies based on the content?
react-virtualized has a solution for that too. You need to use another component named CellMeasurer
for that. We will use another component named CellMeasurerCache
for caching the size of the component.
Now look at the modified rendering process:
import React, {useEffect, useState} from 'react';
import {List, AutoSizer, CellMeasurerCache, CellMeasurer} from 'react-virtualized';
export const LongList = () => {
// SAME AS BEFORE
const cache = new CellMeasurerCache({
defaultWidth: 500,
defaultHeight: 900
});
const renderRow = ({ index, key, style , parent }) => {
return <CellMeasurer
key={key}
cache={cache}
parent={parent}
columnIndex={0}
rowIndex={index}>
<div style={style}>
Name is: {names[index]}
</div>
</CellMeasurer>
}
return <div className={'list'}>
<AutoSizer>
{({ width, height }) => {
return <List
width={width}
height={200}
rowRenderer={renderRow}
rowCount={names.length}
rowHeight={cache.rowHeight}
deferredMeasurementCache={cache}
/>
}}
</AutoSizer>
</div>
}
Conclusion
So there you have it. I just scratched the surface of what’s possible with react-virtualized and how it can help us significantly improve low-performing React applications.
Have a great day!
Get in touch with me via LinkedIn.