Refine MoreComponents data fetching

This commit is contained in:
Sam Becker 2024-01-13 22:19:45 -06:00
parent 4ba2f1cd0c
commit d3e837b4f6
2 changed files with 74 additions and 12 deletions

View File

@ -1,4 +1,4 @@
import { getPhotosCached } from '@/cache';
import { getPhotosCached, getPhotosCountCached } from '@/cache';
import { generateOgImageMetaForPhotos } from '@/photo';
import PhotosEmptyState from '@/photo/PhotosEmptyState';
import { Metadata } from 'next';
@ -14,20 +14,28 @@ export async function generateMetadata(): Promise<Metadata> {
}
export default async function HomePage() {
// Make homepage queries resilient to error on first time setup
const photos = await getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG })
.catch(() => []);
const [
photos,
count,
] = await Promise.all([
// Make homepage queries resilient to error on first time setup
getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_OG }).catch(() => []),
getPhotosCountCached().catch(() => 0),
]);
return (
photos.length > 0
? <div className="space-y-1">
<PhotosLarge photos={photos} />
<MoreComponents
label="More photos"
itemsPerRequest={MAX_PHOTOS_TO_SHOW_OG}
itemsTotalCount={count}
componentLoader={async (limit: number) => {
'use server';
return <PhotosLarge
photos={await getPhotosCached({ limit })}
photos={(await getPhotosCached({ limit }))
.slice(MAX_PHOTOS_TO_SHOW_OG)}
/>;
}}
/>

View File

@ -1,27 +1,81 @@
'use client';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState, useTransition } from 'react';
import Spinner from './Spinner';
export default function MoreComponents({
initialOffset = 1,
itemsPerRequest,
itemsTotalCount,
componentLoader,
label = 'Load more',
triggerOnView = true,
}: {
initialOffset?: number
itemsPerRequest: number
itemsTotalCount: number
componentLoader: (limit: number) => Promise<JSX.Element>
label?: string
triggerOnView?: boolean
prefetch?: boolean
}) {
const [offset] = useState(initialOffset);
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 getPhotosLargeComponentAsync = async () => {
const getMoreComponentsAsync = async () => {
console.log('getMoreComponentsAsync', itemsPerRequest * offset);
return componentLoader(itemsPerRequest * offset);
};
getPhotosLargeComponentAsync().then((component) => {
getMoreComponentsAsync().then((component) => {
setComponents([component]);
});
}, [componentLoader, itemsPerRequest, offset]);
return components;
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>;
}