Using Tanstack Virtual

🌐

This post has been translated by DeepL . Please let us know if there are any mistranslations!

What is Tanstack Virtual?

Tanstack Virtualis a virtualization library for efficiently rendering large scrolling elements.

AnimatedImage.gif

The idea is to only render a certain number of elements in the real DOM, no matter how many there are. The more elements rendered in the DOM, the more performance degrades at some point, so if you have a lot of elements to show, you might want to try this virtualization technique.

Tanstack is not the only virtualization library, there are others such as react-virtualized and react-window.

The problem

Image.png

Previously, we had a grid like the one above, and the number of cards displayed varied depending on the viewport.

As you can see in the Tanstack Virtual example, Tanstack Virtual allows you to apply items in the scroll div as absolute and adjust their position via transform.

In other words, it's not just a matter of putting a Virtualizer on top of the parent, which is what we did before. With Tanstack Virtual, we need to make sure that only one element fits in a row or column.

Solution

The existing list items are a one-dimensional array, and we are rendering them by simply mapping them.

const itemList = [item1, item2, item3, item4, item5, item6, item7];

return <div className='grid grid-cols-1 tablet:grid-cols-3 desktop: grid-cols-5'>
  itemList.map(item => <Card item={item} />)
</div>

As explained above, we need only one element to fit in a row, so we changed the code like below.

let columnSize = 3; // 1, 3, 5

const chunkedItemList = chunkArray(itemList, columnSize);
// [[item1, item2, item3], [item4, item5, item6], [item7]]

return <div>
  chunkedItemList.map(list => <div className='grid grid-cols-1 tablet:grid-cols-3 desktop: grid-cols-5'>
    {list.map(item => <Card item={item} />)}
    </div>)
</div>

We've expanded the existing list of items from a one-dimensional array to a two-dimensional array by columnSize.

Pictorially, it looks like this.

Image.png

The only thing you need to worry about is making sure that there is only one element per line, and how many items you want to put in that one element!

The actual code looks like this

const [columnSize, setColumnSize] = useState(COLUMN_SIZE.desktop);

const list = chunkArray(
  flatPages(data) ?? [],
  columnSize
);

const rowVirtualizer = useVirtualizer({
  count: list?.length,
  getScrollElement: () => document.getElementById("main-section"),
  estimateSize: () => 390,
  overscan: 1,
});

useEffect(() => {
  if (isLabtop) setColumnSize(COLUMN_SIZE.labtop);
  else if (isMobile) setColumnSize(COLUMN_SIZE.mobile);
  else setColumnSize(COLUMN_SIZE.desktop);
}, [isLabtop, isMobile]);

{ }
  rowVirtualizer.getVirtualItems()?.map((virtualRow) => {
    const row = list[virtualRow.index];
    if (!row) return null;
    return (
      <div
        key={virtualRow.key}
        className={`absolute top-0 left-0 w-full`}
        style={{
          transform: `translateY(${virtualRow.start}px)`,
        }}
      >
        <div className="grid grid-cols-1 laptop:grid-cols-3 desktop:grid-cols-5 w-full gap-6 place-items-center items-stretch">
          {row.map((item) => (
            <Card
              key={item.designId}
              item={item}
              isVault={isVault}
            />.
          ))}
        </div>
      </div>
    );
  });
}

As a result, we were able to virtualize only a certain number of them to be drawn in the actual DOM, as shown below!

AnimatedImage.gif