Skip to content

Instantly share code, notes, and snippets.

@OlegLustenko
Created August 22, 2024 14:01
Show Gist options
  • Select an option

  • Save OlegLustenko/3a2c6742e2bcbc81be8d9869cd9dba4d to your computer and use it in GitHub Desktop.

Select an option

Save OlegLustenko/3a2c6742e2bcbc81be8d9869cd9dba4d to your computer and use it in GitHub Desktop.

Revisions

  1. OlegLustenko created this gist Aug 22, 2024.
    115 changes: 115 additions & 0 deletions _list.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    'use client'

    import * as React from 'react'

    import { useVirtualizer } from '@tanstack/react-virtual'
    import { clsx } from 'clsx'
    import { useRouter, useSearchParams } from 'next/navigation'
    import { useRef, useTransition } from 'react'
    import Link from 'next/link'

    export function RowVirtualizerFixed({ rows = [] }: { rows: string[] }) {
    const parentRef = React.useRef(null)
    const totalRef = useRef<string[]>([])
    const searchParams = useSearchParams()

    if (searchParams.get('page') === '1') {
    totalRef.current = rows
    }

    rows.forEach((row) => {
    if (!totalRef.current.includes(row)) {
    totalRef.current.push(row)
    }
    })

    const allRows = totalRef.current

    const hasNextPage = true

    const rowVirtualizer = useVirtualizer({
    count: hasNextPage ? allRows.length + 1 : allRows.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 100,
    overscan: 5,
    initialRect: {
    width: 800,
    height: 800,
    },
    })

    return (
    <div className="h-[800px]">
    <div
    ref={parentRef}
    className="h-5/6 w-full max-w-full overflow-auto"
    >
    <div
    className="relative w-full"
    style={{
    height: `${rowVirtualizer.getTotalSize()}px`,
    }}
    >
    {rowVirtualizer.getVirtualItems().map((virtualRow) => {
    const isLoaderRow = virtualRow.index > allRows.length - 1
    const post = allRows[virtualRow.index]

    return (
    <div
    key={virtualRow.index}
    className={clsx(
    'absolute inset-0 flex w-full items-center justify-center',
    virtualRow.index % 2 ? 'bg-white' : 'bg-amber-200',
    )}
    style={{
    height: `${virtualRow.size}px`,
    transform: `translateY(${virtualRow.start}px)`,
    }}
    >
    {isLoaderRow ? <LoadingMoreButton /> : post}
    </div>
    )
    })}
    </div>
    </div>
    </div>
    )
    }

    const LoadingMoreButton = () => {
    const searchParams = useSearchParams()
    const router = useRouter()
    const [isPending, startTransition] = useTransition()

    const fetchNextPage = () => {
    startTransition(() => {
    router.push(`?page=${+searchParams.get('page')! + 1}`)
    })
    }

    const isMaxPage = searchParams.get('page')! === '10'
    if (isMaxPage) {
    return (
    <div className="focus:ring-blue-700-500 h-full w-full border border-amber-200 bg-blue-300 px-4 py-2 text-center text-2xl text-white focus:outline-none focus:ring-2 focus:ring-offset-2">
    <p className="text-5xl">Max page reached</p>
    <Link
    href="?page=1"
    scroll
    >
    go to page 1
    </Link>
    </div>
    )
    }

    return (
    <button
    className="focus:ring-blue-700-500 h-full w-full border border-amber-200 bg-blue-300 px-4 py-2 text-center text-2xl text-white focus:outline-none focus:ring-2 focus:ring-offset-2"
    onClick={fetchNextPage}
    type="button"
    disabled={isMaxPage}
    >
    {isPending ? <p className="text-5xl">Loading...</p> : 'Load more'}
    </button>
    )
    }
    50 changes: 50 additions & 0 deletions page.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    import React from 'react'
    import { RowVirtualizerFixed } from '@/app/workshops/rsc-virtualized/_list'
    import { clsx } from 'clsx'
    import { redirect } from 'next/navigation'

    async function fetchServerPage(
    limit: number,
    offset: number = 0,
    ): Promise<string[]> {
    const rows = new Array(limit)
    .fill(0)
    .map((e, i) => `Async loaded row #${i + offset * limit}`)

    await new Promise((resolve) => {
    setTimeout(resolve, 600)
    })

    return rows
    }

    const Page = async ({ searchParams }: { searchParams: { page: string } }) => {
    if (!searchParams.page) {
    redirect('?page=1')
    }

    const isMaxPage = +searchParams.page > 10

    if (isMaxPage) {
    redirect('?page=10')
    }

    const offset = searchParams.page === '1' ? 0 : +searchParams.page - 1
    const rows = await fetchServerPage(10, offset)

    return (
    <div>
    <h1 className={clsx('text-center text-2xl text-white')}>
    Virtualization
    {isMaxPage && (
    <span className="text-center text-4xl text-fuchsia-600">
    Max page reached
    </span>
    )}
    </h1>
    <RowVirtualizerFixed rows={rows} />
    </div>
    )
    }

    export default Page