T
TanStack•5mo ago
helpful-purple

infinite-scroll virtualizer

Does this approach provide the benefits of virtualization while maintaining our grid layout?
import { useWindowVirtualizer } from "@tanstack/react-virtual";

const GridWithVirtualization = () => {
const items = [...];

const totalCount = 1000;

// Window-based virtualizer
const virtualizer = useWindowVirtualizer({
count: items.length + (items.length < totalCount ? 1 : 0),
estimateSize: () => 350,
overscan: 5,
measureElement: (el) => el?.getBoundingClientRect().height || 350,
});

// Load more when reaching bottom
useEffect(() => {
const [lastItem] = [...virtualizer.getVirtualItems()].reverse();
if (lastItem?.index >= items.length - 1 && items.length < totalCount) {
loadMoreData();
}
}, [virtualizer.getVirtualItems()]);

return (
<div className="grid grid-cols-2 gap-4">
{virtualizer.getVirtualItems().map(virtualRow => {
const item = items[virtualRow.index];
return item ? <ItemCard key={virtualRow.index} item={item} /> : <Skeleton />;
})}
</div>
);
};
import { useWindowVirtualizer } from "@tanstack/react-virtual";

const GridWithVirtualization = () => {
const items = [...];

const totalCount = 1000;

// Window-based virtualizer
const virtualizer = useWindowVirtualizer({
count: items.length + (items.length < totalCount ? 1 : 0),
estimateSize: () => 350,
overscan: 5,
measureElement: (el) => el?.getBoundingClientRect().height || 350,
});

// Load more when reaching bottom
useEffect(() => {
const [lastItem] = [...virtualizer.getVirtualItems()].reverse();
if (lastItem?.index >= items.length - 1 && items.length < totalCount) {
loadMoreData();
}
}, [virtualizer.getVirtualItems()]);

return (
<div className="grid grid-cols-2 gap-4">
{virtualizer.getVirtualItems().map(virtualRow => {
const item = items[virtualRow.index];
return item ? <ItemCard key={virtualRow.index} item={item} /> : <Skeleton />;
})}
</div>
);
};
3 Replies
genetic-orange
genetic-orange•5mo ago
Not really, i think you would like something like this
function List() {
const parentRef = useRef(null);
const columnCount = 2;
const count = 10000;
const columns = Array.from(Array(columnCount).keys());
const virtualizer = useVirtualizer({
useAnimationFrameWithResizeObserver: true,
count: Math.ceil(count / columnCount),
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
overscan: 5,
gap: 16,
});

const renderRowItem = (rowIndex: number) => {
return columns.map((col) => {
const i = rowIndex * columnCount + col;
if (i < count) {
const key = virtualizer.options.getItemKey(i);
return (
<div
key={key as any}
style={{ background: '#eee' }}
>
Row {i}
</div>
);
}
return null;
});
};

return (
<div style={{ display: 'flex' }}>
<div
tabIndex={0}
ref={parentRef}
style={{
height: 600,
width: 800,
overflow: 'auto',
}}
>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.key as any}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
display: 'grid',
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
gap: 16,
}}
>
{renderRowItem(virtualRow.index)}
</div>
))}
</div>
</div>
</div>
);
}
function List() {
const parentRef = useRef(null);
const columnCount = 2;
const count = 10000;
const columns = Array.from(Array(columnCount).keys());
const virtualizer = useVirtualizer({
useAnimationFrameWithResizeObserver: true,
count: Math.ceil(count / columnCount),
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
overscan: 5,
gap: 16,
});

const renderRowItem = (rowIndex: number) => {
return columns.map((col) => {
const i = rowIndex * columnCount + col;
if (i < count) {
const key = virtualizer.options.getItemKey(i);
return (
<div
key={key as any}
style={{ background: '#eee' }}
>
Row {i}
</div>
);
}
return null;
});
};

return (
<div style={{ display: 'flex' }}>
<div
tabIndex={0}
ref={parentRef}
style={{
height: 600,
width: 800,
overflow: 'auto',
}}
>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.key as any}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
display: 'grid',
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
gap: 16,
}}
>
{renderRowItem(virtualRow.index)}
</div>
))}
</div>
</div>
</div>
);
}
helpful-purple
helpful-purpleOP•5mo ago
hey, thanks for the detailed response!! 🙂 is there any way to remove scroll from the container that wraps the content? as it should only rely on the window scroll (from the parent on the page) something like this
<div ref={parentRef} className="relative">
<div
style={{
height: `${virtualHeight}px`,
width: "100%",
position: "relative",
}}
>
{children}
</div>
</div>
<div ref={parentRef} className="relative">
<div
style={{
height: `${virtualHeight}px`,
width: "100%",
position: "relative",
}}
>
{children}
</div>
</div>
genetic-orange
genetic-orange•5mo ago
Yes, change to use useWindowVirtualizer and aling the code with it

Did you find this page helpful?