Vercel/src/components/MoreComponents.tsx
2024-01-13 22:19:45 -06:00

82 lines
2.1 KiB
TypeScript

'use client';
import { useCallback, useEffect, useRef, useState, useTransition } from 'react';
import Spinner from './Spinner';
export default function MoreComponents({
itemsPerRequest,
itemsTotalCount,
componentLoader,
label = 'Load more',
triggerOnView = true,
}: {
itemsPerRequest: number
itemsTotalCount: number
componentLoader: (limit: number) => Promise<JSX.Element>
label?: string
triggerOnView?: boolean
prefetch?: boolean
}) {
const [offset, setOffset] = useState(1);
const [components, setComponents] = useState<JSX.Element[]>([]);
const [isPending, startTransition] = useTransition();
const buttonRef = useRef<HTMLButtonElement>(null);
const advance = useCallback(() => startTransition(() => {
setOffset(o => o + 1);
}), []);
useEffect(() => {
const getMoreComponentsAsync = async () => {
console.log('getMoreComponentsAsync', itemsPerRequest * offset);
return componentLoader(itemsPerRequest * offset);
};
getMoreComponentsAsync().then((component) => {
setComponents([component]);
});
}, [componentLoader, itemsPerRequest, offset]);
useEffect(() => {
// Only add observer if button is rendered
if (buttonRef.current) {
const observer = new IntersectionObserver(e => {
if (
triggerOnView &&
e[0].isIntersecting &&
!isPending
) {
advance();
}
}, {
root: null,
threshold: 0,
});
observer.observe(buttonRef.current);
return () => observer.disconnect();
}
}, [triggerOnView, advance, isPending]);
const showMoreButton = itemsTotalCount > itemsPerRequest * offset;
return <div className="space-y-4">
{components}
{showMoreButton &&
<button
ref={buttonRef}
className="block w-full subtle"
onClick={!triggerOnView ? advance : undefined}
disabled={triggerOnView || isPending}
>
{isPending
? <span className="relative inline-block translate-y-[3px]">
<Spinner size={16} />
</span>
: label}
</button>}
</div>;
}