Render Huge List With react-window
https://github.com/bvaughn/react-window
Smooth user interface has always been an important aspect of a web app, even a digital product in general. As the product grows, so does its data. Let’s say we have a list of people on a page. Traditionally, we loop it with map()
method like this:
import React, from 'react';
import './App.css';
const people = [/* a list of users */]
const App = () => {
return (
<div style={{ height: "150px", width: "200px", overflowY: "auto" }}>
{people.map((person, index) => (
<div style={{ height: "35px", width: "100%" }} key={index}>
{person.name}
</div>
))}
</div>
);
}
export default App;
But what if there are a hundred of them? Or even a thousand? They would dramatically reduce our app’s performance and causes a bottleneck. Rendering rows on the list utilizes CPU intensively and the worst case—your browser would choke it to death1. A slow app would interrupt the user experience, of course. Lucky enough if the device can survive the heavy load. In order to keep users engaged, we have to avoid letting it happen.
Conditional rendering with react-window
react-window is a package of React components capable of rendering a large list of data. You have probably heard of react-virtualized by Brian Vaughn. It has the same purpose—but he made react-window
a complete rewrite, leaving only the core feature from its precursor. Thus, the package size is reduced from 35.1 kB to 6.2 kB (minified + gzipped)2.
Usage
FixedSizeList
If you know how much height you need for each row, import FixedSizeList
component from react-window
. Next, pass props as follows:
height
: our container height in pixelwidth
: our container width in pixelitemCount
: how many items we are going to renderitemSize
: height of each row in pixel
Suppose that we want to make a list of a thousand artists around the world. Consider the example below:
// in your App.jsx
import React from "react"
import { FixedSizeList } from "react-window"
const artists = [
/* your data */
]
const App = () => {
const Row = ({ index, style }) => <div style={style}>{artists[index]}</div>
return (
<FixedSizeList
height={150}
width={300}
itemCount={artists.length}
itemSize={65}
>
{Row}
</FixedSizeList>
)
}
export default App
Inside the component, we add another component named Row
. This child element inherits two props; index
and style
. Pass style
prop into the element’s own inline style
. If you are curious what it contains, this is what it looks like from the third row:
props: {
index: 2,
style: {
position: "absolute",
left: 0,
top: 0,
height: 130, /* itemSize × index */
width:"100%"
}
}
Variable Size List
We have made a list of artists. How about giving the featured ones spotlight by making their row bigger? In this case, use VariableSizeList
. All props are the same, except itemSize
. It accepts a function
with index
passed as an argument that returns number
eventually.
// in your App.jsx
import React from "react"
import { VariableSizeList } from "react-window"
const artists = [
{
name: "Above & Beyond",
featured: true,
},
{
name: "Andrew Bayer",
featured: false,
},
/* etc... */
]
const App = () => {
const Row = ({ index, style }) => (
<div style={style}>{artists[index].name}</div>
)
return (
<VariableSizeList
height={150}
width={300}
itemCount={artists.length}
itemSize={index => (artists[index].featured ? 100 : 65)}
>
{Row}
</VariableSizeList>
)
}
export default App
List with AutoSizer
If we already have a parent element as the container, width
and height
prop values can be set adaptively using react-virtualized-auto-sizer.
// in your App.jsx
import React from "react"
import { FixedSizeList } from "react-window"
import AutoSizer from "react-virtualized-auto-sizer"
const artists = [
/* your data */
]
const App = () => {
const Row = ({ index, style }) => <div style={style}>{artists[index]}</div>
return (
<div
style={{
height: "200px",
width: "300px",
}}
>
<AutoSizer>
{({ height, width }) => (
<FixedSizeList
height={height}
width={width}
itemCount={artists.length}
itemSize={65}
>
{Row}
</FixedSizeList>
)}
</AutoSizer>
</div>
)
}
export default App
Horizontal list and grid
react-window
also supports horizontal layout and grid for multidimensional list, allowing the user to scroll both vertically and horizontally. You can check out react-window.now.sh to see the example.
Under the hood
react-window
implements a similar technique on 3D computer graphics called Occlusion Culling that reveals objects only visible to the user and avoids unnecessary render3. It has a container as tall as the sum of entire rows’ height. Each row is absolute
-ly positioned (pun intended) and its top
property is responsible for conditional rendering. There’s also Frustum Culling, a less strict one. However, both are important to keep user experience frictionless. Have a look at Horizon Zero Dawn below.
Nevertheless, keep in mind that this is not our Swiss-Army-knife for every list. If the number of rows is still reasonable, there is no need to over-engineer it.
-
Stack Overflow, performance - Why is the rendering of HTML a cpu intensive process? [website] https://stackoverflow.com/questions/4778356/why-is-the-rendering-of-html-a-cpu-intensive-process, (accessed June 9 2020)
↩ -
BundlePhobia, react-virtualized@9.21.2 [website], https://bundlephobia.com/result?p=react-virtualized@9.21.2, (accessed June 9 2020); BundlePhobia, react-window@1.8.5 [website], https://bundlephobia.com/result?p=react-window@1.8.5, (accessed June 9 2020)
↩ -
Unity, Manual: Occlusion Culling [website], https://docs.unity3d.com/Manual/OcclusionCulling.html, (accessed June 9 2020)
↩