Virtualize React Components with React Virtual

The author
Stephen Castle4 years ago

What is Virtualization in React

Virtualization is a method of deferring component rendering until the component is actually needed in the viewport. It is commonly used for very large lists of items where normally in React you would map over the list and render JSX for every list item, if you were to render enough of them eventually it would start to harm performance because of having so many DOM nodes created. Virtualization allows you to write React code with such large lists, but only render a much smaller and more reasonable number of DOM nodes, by rendering only what is needed to fill the available screen real-estate.

You could implement this yourself, but for the most part React users have relied on two great libraries to accomplish this. react-virtualized, and later react-window. Written by the same author, Brian Vaughn, they are great at solving the virtualization problem. Then just the last month a new virtualization library called react-virtual was released by Tanner Linsley, another great open source author who has written some other really cool packages like React Query. React Virtual has a modern hooks based API and some powerful flexibility which will likely make it a fairly popular way of handling virtualization in the near future so it might be a good idea to get familiar with it.

How to use React Virtual

Let's build a simple app to get a feel for using react-virtual. We will build this Birthday Reminder App to help us remember friend's birthdays. It will list all of our friends and when their next birthday occurs, and since we are very popular we have over 5,000 friends to list! And you're making new friends all the time, who knows how long it will be till all your friends crash your React app? Well this is a perfect time to use react-virtual, we know that we want to list all 5,000 friends but we don't want the hassle of paginating our list or having a slow app.

Here is a starter project with all of the CSS and components we will need for the demo so we can focus on react-virtual. Feel free to fork it and follow along.

Install React Virtual from NPM and Import

$ npm i --save react-virtual
# or
$ yarn add react-virtual
import { useVirtual } from "react-virtual";

Using the useVirtual Hook

The core of react-virtual is the useVirtual hook. Depending on how you configure it, it allows you to build most common types of virtualized features. Let's build a simple one now to get a feel for it.

const parentRef = React.useRef();

const rowVirtualizer = useVirtual({
  size: data.length,
  parentRef,
  estimateSize: React.useCallback(() => 35, []),
});

This is enough to create our rowVirtualizer, so now let's break down each part of the configuration object being passed into rowVirtualizer since this is pivotal in determining how the hook functions.

Connecting the Parent Ref

You'll notice that in the last step we created a ref called parentRef. This needs to be a ref attached to the parent component of your virtualized list. This is a required field so make sure you hook it up. In our demo app we can add it to the parent div like this.

const rowVirtualizer = useVirtual({
  size: data.length,
  parentRef, //highlight-line
  estimateSize: React.useCallback(() => 35, []),
});

Size

The size here is referring to the number of items to be virtualized. I don't know why it's called size as it's very easy to confuse with the estimateSize function. So generally speaking this will be the length of the list you want to virtualize.

const rowVirtualizer = useVirtual({
  size: data.length, //highlight-line
  parentRef,
  estimateSize: React.useCallback(() => 35, []),
});

Estimating List Item Size with estimateSize

This is a very important setting, and it's required. You MUST pass a React.useCallback function to avoid strange behaviors. And it should return the estimated height of the elements you expect to have in your list after they are rendered. React Virtualize uses this to provide you with height values and calculate a lot of things under the hood. So if you don't set it to a reasonable value expect to see some strange things happening as you scroll your list.

const rowVirtualizer = useVirtual({
  size: data.length,
  parentRef,
  estimateSize: React.useCallback(() => 35, []), //highlight-line
});

Rendering Items From the Virtual List

Once your virtualizer is configured this library leaves everything else up to you, it's a bit more hands off than react-window or react-virtualized. Which can be great if you need the flexibility. However, if you have a use case that is easily solved by react-window it might be better to use that instead. If you need more, or you're just here to learn about react-virtual let's proceed.

import React from "react";
import "./styles.css";
import Card from "./Card";
import data from "./users";

import { useVirtual } from "react-virtual";

export default function App() {
  const parentRef = React.useRef();

  const rowVirtualizer = useVirtual({
    size: data.length,
    parentRef,
    estimateSize: React.useCallback(() => 220, [])
  });
  //highlight-start
  return (
    <div className="App">
      <img src="./cake.png" className="logo" alt="Happy birthday cake!" />
      <div
        ref={parentRef}
        style={{
          height: `100vh`,
          width: "100%",
          overflow: "auto"
        }}
      >
        <div
          className="ListInner"
          style={{
            // Set the scrolling inner div of the parent to be the
            // height of all items combined. This makes the scroll bar work.
            height: `${rowVirtualizer.totalSize}px`,
            width: "100%",
            position: "relative"
          }}
        >
          {// The meat and potatoes, an array of the virtual items
          // we currently want to render and their index in the original data.
          {rowVirtualizer.virtualItems.map(virtualRow => (
            <div
              key={virtualRow.index}
              style={{
                position: "absolute",
                top: 0,
                left: 0,
                width: "100%",
                // Positions the virtual elements at the right place in container.
                height: `${virtualRow.size}px`,
                transform: `translateY(${virtualRow.start}px)`
              }}
            >
              <Card person={data[virtualRow.index]} />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
  //highlight-end
}

Final Thoughts

This is just a basic demo using some real components to demonstrate a simple use case for react-virtual. It looks like a powerful set of primitives for building virtualization features. Since it's a very new library it may mature and improve over time, but it's already looking pretty nice. So should you use react-window or this? I think it depends on if you need the flexibility provided here or if your problem can be solved with a plug and play solution. Either way I'd give React Virtual a try because it's alot of fun.

Finished Project