import { useZeroVirtualizer, useHistoryScrollState, type GetPageQueryOptions, type GetSingleQueryOptions, } from '@rocicorp/zero-virtual/react'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import styles from './App.module.css'; import {ItemCount} from './ItemCount.tsx'; import {ItemDetail} from './ItemDetail.tsx'; import {queries, type ItemStart, type ListContextParams} from './queries.ts'; import type {Item} from './schema.ts'; import {SortControls} from './SortControls.tsx'; import {useHash} from './use-hash.ts'; const ITEM_HEIGHT = 48; const dateFormatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short', }); function getRowKey(item: Item): string { return item.id; } function toStartRow(item: Item): ItemStart { return { id: item.id, created: item.created, modified: item.modified, }; } function estimateSize(): number { return ITEM_HEIGHT; } function getQueryOptions(settled: boolean) { return {ttl: settled ? '5m' : 'none'} as const; } function getSingleQuery({id, settled}: GetSingleQueryOptions) { return { query: queries.item.getSingleQuery({id}), options: getQueryOptions(settled), } as const; } export function App(): React.ReactNode { const [hash, setHash] = useHash(); const permalinkID = hash || null; const [sortField, setSortField] = useState<'created' | 'modified'>( 'modified', ); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); const toggleSortField = useCallback(() => { setSortField(f => (f === 'modified' ? 'created' : 'modified')); }, []); const toggleSortDirection = useCallback(() => { setSortDirection(d => (d === 'asc' ? 'desc' : 'asc')); }, []); const parentRef = useRef(null); const getScrollElement = useCallback(() => parentRef.current, []); const listContextParams = useMemo( () => ({sortField, sortDirection}), [sortField, sortDirection], ); const getPageQuery = useCallback( ({limit, start, dir, settled}: GetPageQueryOptions) => { return { query: queries.item.getPageQuery({ limit, start, dir, listContextParams, }), options: getQueryOptions(settled), }; }, [listContextParams], ); const [scrollState, onScrollStateChange] = useHistoryScrollState(); const {virtualizer, rowAt, estimatedTotal, total} = useZeroVirtualizer({ listContextParams, getScrollElement, getRowKey, estimateSize, getPageQuery, getSingleQuery, toStartRow, permalinkID, scrollState, onScrollStateChange, onSettled: useCallback(() => { console.log('onSettled'); }, []), }); const virtualItems = virtualizer.getVirtualItems(); return (

Zero Virtual Demo

{/* Scrollable viewport */}
{/* Total height spacer */}
{virtualItems.map(virtualRow => { const row = rowAt(virtualRow.index); if (row === undefined) { // placeholder return (
Loading...
); } return (
setHash(row.id === permalinkID ? '' : row.id)} > {row.title} {dateFormatter.format(row[sortField])}
); })}
{permalinkID && ( setHash('')} /> )}
); }