Applying Tanstack Virtual

profile image

Optimize DOM by virtualizing lists with Tanstack Virtual!

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

What is Tanstack Virtual?

Tanstack Virtual is a virtualization library for efficiently rendering large scrolling elements.

tanstack-virtual-example1

It's a technique that renders only a fixed number of elements in the actual DOM, no matter how many elements there are. As the number of elements rendered in the DOM increases, performance degrades dramatically at some point, so this virtualization technique can be applied when there are many elements to display.

Besides Tanstack, there are other virtualization libraries such as react-virtualized and react-window.

Problem

Image.png

Previously, it was structured as a grid as shown above, and the number of cards displayed varied depending on the viewport.

As you can see from the Tanstack Virtual examples, when using Tanstack Virtual, items inside the scroll div are applied with absolute positioning, and their positions are adjusted through transform.

In other words, it wasn't a matter of just wrapping the parent with Virtualizer in the existing approach. When using Tanstack Virtual, you need to ensure that only one element goes into each row or column.

Solution

The existing list items were in a one-dimensional array, and they were simply rendered by mapping over them.

typescript
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, since only one element should go into a row, I changed the code as follows.

typescript
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>

I transformed the existing item list from a one-dimensional array into a two-dimensional array by splitting it by columnSize.

Looking at it in a diagram, it feels like this.

Image.png

What you need to pay attention to is making sure only one element goes into a line, and how many items to put into that one element!

The actual code is as follows.

typescript
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, I was able to virtualize so that only a specific number of items are actually rendered in the DOM, as shown below!

tanstack-virtual-example2

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0