Virtual React Example: Grid (Hook)

Virtualized dynamic-sized grids are supported, just call useVirtual two times for rows and columns. Important limitation: masonry mode is not supported, so height of rows is taken from first rendered column and width of columns is taken from first rendered row.

import { Fragment, memo } from "react";

import {
    useVirtual,
    useComponentSubscription,
    mapVisibleRangeWithOffset,
    createGridItemRef
} from "@af-utils/virtual-react";

import { VirtualScroller, VirtualScrollerEvent } from "@af-utils/virtual-core";

import css from "./style.module.css";

const events = [
    VirtualScrollerEvent.RANGE,
    VirtualScrollerEvent.SCROLL_SIZE,
    VirtualScrollerEvent.SIZES
] as const;

const Cell = memo<{
    rows: VirtualScroller;
    cols: VirtualScroller;
    rowI: number;
    colI: number;
    rowOffset: number;
    colOffset: number;
}>(({ rows, cols, rowI, colI, rowOffset, colOffset }) => (
    <div
        ref={createGridItemRef(rows, rowI, cols, colI)}
        className={css.cell}
        style={{
            width: Math.max(colI ** 2 % 256, 190),
            padding: `${Math.max(rowI ** 2 % 64, 30)}px 0`,
            transform: `translateX(${colOffset}px) translateY(${rowOffset}px)`
        }}
    >
        <div className={css.cellContent}>
            <span>row:</span>
            {rowI}
            <span>col:</span>
            {colI}
        </div>
    </div>
));

const GridItems = ({
    rows,
    cols
}: {
    rows: VirtualScroller;
    cols: VirtualScroller;
}) => {
    useComponentSubscription(rows, events);
    useComponentSubscription(cols, events);

    return (
        <div
            className={css.gridItems}
            style={{
                height: rows.scrollSize,
                width: cols.scrollSize
            }}
        >
            {mapVisibleRangeWithOffset(rows, (rowI, rowOffset) => (
                <Fragment key={rowI}>
                    {mapVisibleRangeWithOffset(cols, (colI, colOffset) => (
                        <Cell
                            key={colI}
                            rows={rows}
                            cols={cols}
                            rowOffset={rowOffset}
                            colOffset={colOffset}
                            rowI={rowI}
                            colI={colI}
                        />
                    ))}
                </Fragment>
            ))}
        </div>
    );
};

const SIZE = 50000;

const scrollModelTo = (model: VirtualScroller, value: string | undefined) => {
    if (value !== undefined) {
        const idx = Number.parseInt(value, 10);

        if (!Number.isNaN(idx)) {
            model.scrollToIndex(idx, true);
        }
    }
};

const GridHook = () => {
    const rows = useVirtual({
        itemCount: SIZE,
        estimatedItemSize: 120,
        overscanCount: 2
    });

    const cols = useVirtual({
        itemCount: SIZE,
        estimatedItemSize: 200,
        overscanCount: 2,
        horizontal: true
    });

    return (
        <div className={css.root}>
            <form
                className={css.form}
                onSubmit={e => {
                    e.preventDefault();
                    scrollModelTo(
                        e.currentTarget.type.value === "row" ? rows : cols,
                        e.currentTarget.index.value
                    );
                }}
            >
                <select name="type">
                    <option value="row">Row</option>
                    <option value="col">Col</option>
                </select>
                <input
                    placeholder="index"
                    type="number"
                    name="index"
                    min={0}
                    max={SIZE}
                    className="w-28"
                />
                <button type="submit" className={css.btn}>
                    Scroll
                </button>
            </form>
            <div
                className={css.grid}
                ref={el => {
                    rows.setScroller(el);
                    cols.setScroller(el);
                }}
            >
                <GridItems rows={rows} cols={cols} />
            </div>
        </div>
    );
};

export default GridHook;