What is Tanstack Virtual?
Tanstack Virtual is a virtualization library for efficiently rendering large scrolling elements.
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
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.
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.
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.
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.
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!