Back

Virtualizing Large Data Lists with react-window

Virtualizing Large Data Lists with react-window

Rendering large dataset in the DOM without the right tools can be expensive as it has some adverse effect on the web page performance; it slows down runtime, network requests, memory performance. In this tutorial, we’ll cover on how render large amount of data, either from an external API or a dummy data file within our application. We’ll use a React library, react-window, to render this large amount of data. Then we’ll build a simple e-commerce app displaying a list of products.

This article will help web developers to build a better web application that displays and handles large amount of data in either a list format or grid format. It will introduce and explore solutions to the shortcomings with infinite scroll or pagination provided in virtualization of data. It will also cover the best UI/UX practices needed when building applications that render large data within it.

What is virtualization?

Virtualization as a scrolling technique is the rendering process that keeps track of the scrolling position of a user, and visually displays only the DOM contents within that current viewport’s position. This technique simply involves handling large dataset in an infinite scroll pattern but incrementally load and render the long lists of data just as the data enters the viewport. This concept presents developers with all the performance advantages of pagination while at the same time providing the UX benefits gained from infinite scrolling.

Using virtualization is a common solution used in rendering large amount of posts in social media applications, delivering large news data in news media applications, and also in bookkeeping (Accounting) software. The endless two dimensional tile scrolling in Map applications also involves virtual scrolling which aids easy display of the constant changing locations of the users when using it. And virtualization supports displaying these large datasets in grid format or list (spreadsheets-like) format.

A simple implementation of virtualization involves a pre-calculation of the aggregate height of the list of data using the Intersection Observer API provided by the Web API and multiplying it with total count of the list items. But for this tutorial, we’ll use another easier approach, by using react-window.

React-window is a simplified version of react-virtualized package for easily implementing virtualization in React applications through the APIs (components) provided by the library. According to the docs, React window works by only rendering part of a large dataset (just enough to fill the viewport) which reduces the amount of work (and time) required to render the initial view and to process incoming updates to the UI, and also reduces the memory footprint by avoiding over-allocation of DOM nodes.

The Four react-window Components

Let’s go through the four available components that can be used to render lists of items when using react-window within React applications.

  • FixedSizeList — This component helps render elements of fixed size defined through the index prop. Common available props that can be passed into this component are: useIsScrolling, itemCount , itemSize, itemData, innerElementType, etc

  • VariableSizeList — This component accepts the same props as the FixedSizeList component, but with additional props like an estimatedItemSize, and itemSize which takes in a function in this case and returns the size of a item in the direction being windowed

  • FixedSizeGrid — With this component, we’re rendering using dimensions: vertical (columns) and horizontal (rows) directions. Another difference is that we’ll have to add a data’s count. Common props are rowCount, rowWidth, columnCount, columnWidth, height, width e.t.c

  • VariableSizeGrid — This component is as FixedSizeGrid, with additional props: columnWidth & rowHeight which accepts functions as their prop values.

For this tutorial, we’ll focus on using the FixedSizeList component, which gives a beginner-friendly introduction to using react-window within our React applications (a simple e-commerce application displaying a list of products.).

Implementing react-window In React Apps

Now let’s install react-window using yarn in a React application. In this tutorial, react-window will be installed as a dependency, while the types for it will be installed as a devDependency. We’ll also install faker.js as dependency:

yarn add react-window faker
yarn add -D @types/react-window

After the installation has successfully been completed, go ahead to open the ./src/App.js file and include the following JSX markup:

import { useState } from "react";
import * as faker from "faker";

const App = () => {
  const [data, setData] = useState(() =>
    Array.from({ length: 1200 }, faker.commerce.product)
  );

  return (
    <main>
      <ul style={{ width: "400px", height: "700px", overflowY: "scroll" }}>
        {data.map((product, i) => (
          <li key={i + product}>{product}</li>
        ))}
      </ul>
    </main>
  );
};

In the above code, we defined the structural markup for the list of data being rendered into the DOM. Then passed a basic style prop to the ul tag to add a fixed width and height styles, also with an overflow set to scroll on it. Also we’ll use the React hook, useState, to mange our data state provided by faker.js’s dummy data. We create an array of size 1200 from it, and populate the array with enough data from faker.js.

Next, we’ll start implementing virtualization using the react-window’s component FixedSizeList. This component is responsible for rendering an individual item of a fixed size specified by the index prop within the list:

import { useState } from "react";
import * as faker from "faker";
import { FixedSizeList as List } from "react-window";

const App = () => {
  const [data, setData] = useState(() =>
    Array.from({ length: 1200 }, faker.commerce.product)
  );

  return (
    <main>
      <List
        innerElementType="ul"
        itemData={data}
        itemCount={data.length}
        itemSize={20}
        height={700}
        width={400}
      >
        {({data, index, style }) => {
          return (
            <li style={style}>
              {data[index]}
            </li>
          );
        }}
      </List>
    </main>
  );
};

The List element uses two markup approaches defined by it’s props, the innerElementType and outerElementType props. Their default values are divs but in our use case, we’ll set the outerElementType to ul. Then we’ll pass in li as the children prop to the List component, providing it with a necessary styles parameters (absolute positioning styles, etc.). Also providing a width and height props to pass the previous styles we earlier defined through the style prop. Then using the itemCount prop, we are creating an imaginary array with the same length of our state, data. Then we explicitly make this data available to the li elements through the itemData prop.

Let’s modify the unique value for the key prop on the li elements by the using the itemKey prop made available on the List component. We’ll use the faker.datatype.uuid function to generate random UUID and populate the itemKey prop with it.

...
const App = () => {
  ...
  return (
    <main>
      <List
        innerElementType="ul"
        itemData={data}
        itemCount={data.length}
        itemKey={faker.datatype.uuid}
        itemSize={20}
        height={700}
        width={400}
      >
        {({data, index, style }) => {
          return (
            <li style={style}>
              {data[index]}
            </li>
          );
        }}
      </List>
    </main>
  );
};

This code will make sure we’re using React’s lists and key best practices as defined by the docs. The term “key” is used to describe a special string attribute that needs to be included when creating lists of elements in React applications.

With react-window, we can go further to define more structured UI elements within the children prop passed into the List component without tampering with the optimised performance.

...
const App = () => {
 const [data, setData] = useState(() =>
    Array.from({ length: 1200 }, () => ({
      productImg: faker.image.business(200, 600, true),
      product: faker.commerce.product(),
      productPrice: faker.commerce.price(2, 22, 1, "$"),
      productDescription: faker.commerce.productAdjective(),
    }))
  );  return (
    <main>
      <List
        innerElementType="ul"
        itemData={data}
        itemCount={data.length}
        itemKey={faker.datatype.uuid}
        itemSize={20}
        height={700}
        width={400}
      >
        {({data, index, style }) => {
          return (
             <li className="py-4 border-2 border-indigo-400 flex" {...props}>
        <img className="h-10 w-10 rounded-full" src={productImg} alt="" />
        <div className="ml-3">
          <p className="space-x-1">
            <span className="text-base text-gray-900 whitespace-nowrap">
              {product}
            </span>
            <span
              className="text-gray-600 text-base font-extrabold"
              style={{
                backgroundImage:
                  "linear-gradient(transparent 50%, pink 50%, pink 85%,transparent 85%,transparent 100%)",
              }}
            >
              {productPrice}
            </span>
          </p>
          <p className="text-sm text-gray-500">{productDescription}</p>
        </div>
      </li>
          );
        }}
      </List>
    </main>
  );
};

Here, we updated the state of our application with more data fields to help model an online shop that has the following attributes: image (productImg), name (product), price (productPrice) and description (productDescription) of its products. We also defined more elements to hold this additional data.

(Note: To replicate the UI styles used in this tutorial, make sure to include the below tailwind.css CDN to the /public/index.html file.)

<link
      href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
      rel="stylesheet"
    />

If you inspect the example List component using browser Devtools, you’ll notice that no matter how lengthy we scroll down the long list of the items within our application, the number of items in its parent div never changes. What does change is how far each one is absolutely positioned from the top. Thus, making sure we see only the UI elements for the items in the viewport.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

Conclusion

In this tutorial, we’ve discussed on how to leverage virtualization to optimize the DOM when rending large amount of dataset into it. Then introduced react-window as a React package that efficiently implements virtaulization behind the hood, to render a large dataset and at the same time speeds up performance targets within our application. It does this by rendering only the data that are within the viewport of the browser. Get the code used in this tutorial on GitHub.

Resources