️ Use infinite query on polls page (#1200)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Luke Vella 2024-07-10 08:34:00 +01:00 committed by GitHub
parent e985a5db9f
commit 43c450aa91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 182 additions and 66 deletions

View file

@ -22,6 +22,7 @@ import {
import { PollStatusBadge } from "@/components/poll-status";
import { Spinner } from "@/components/spinner";
import { Trans } from "@/components/trans";
import { VisibilityTrigger } from "@/components/visibility-trigger";
import { trpc } from "@/utils/trpc/client";
function PollCount({ count }: { count?: number }) {
@ -29,11 +30,14 @@ function PollCount({ count }: { count?: number }) {
}
function FilteredPolls({ status }: { status: PollStatus }) {
const { data, isFetching } = trpc.polls.list.useQuery(
const { data, fetchNextPage, hasNextPage } =
trpc.polls.infiniteList.useInfiniteQuery(
{
status,
limit: 30,
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
keepPreviousData: true,
},
);
@ -43,12 +47,19 @@ function FilteredPolls({ status }: { status: PollStatus }) {
}
return (
<div
className={cn({
"animate-pulse": isFetching,
})}
>
<PollsListView data={data} />
<div className="space-y-6">
<ol className="space-y-4">
{data.pages.map((page, i) => (
<li key={i}>
<PollsListView data={page.polls} />
</li>
))}
</ol>
{hasNextPage ? (
<VisibilityTrigger onVisible={fetchNextPage} className="mt-6">
<Spinner />
</VisibilityTrigger>
) : null}
</div>
);
}

View file

@ -0,0 +1,51 @@
import React from "react";
export function useVisibilityTrigger<T extends Element>(onVisible: () => void) {
const triggerRef = React.useRef<T | null>(null);
React.useEffect(() => {
const currentTriggerRef = triggerRef.current;
if (!currentTriggerRef) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
onVisible();
}
},
{
root: null, // Use the viewport as the root
rootMargin: "0px",
threshold: 1.0, // Trigger when 100% of the element is visible
},
);
observer.observe(currentTriggerRef);
return () => {
if (currentTriggerRef) {
observer.unobserve(currentTriggerRef);
}
};
}, [onVisible]);
return triggerRef;
}
export function VisibilityTrigger({
children,
onVisible,
className,
}: {
children: React.ReactNode;
onVisible: () => void;
className?: string;
}) {
const triggerRef = useVisibilityTrigger<HTMLDivElement>(onVisible);
return (
<div className={className} ref={triggerRef}>
{children}
</div>
);
}

View file

@ -93,6 +93,58 @@ export const polls = router({
},
});
}),
infiniteList: possiblyPublicProcedure
.input(
z.object({
status: z.enum(["all", "live", "paused", "finalized"]),
cursor: z.string().optional(),
limit: z.number(),
}),
)
.query(async ({ ctx, input }) => {
const { cursor, limit, status } = input;
const polls = await prisma.poll.findMany({
where: {
userId: ctx.user.id,
status: status === "all" ? undefined : status,
},
orderBy: [
{
createdAt: "desc",
},
{
title: "asc",
},
],
cursor: cursor ? { id: cursor } : undefined,
take: limit + 1,
select: {
id: true,
title: true,
location: true,
timeZone: true,
createdAt: true,
status: true,
userId: true,
participants: {
select: {
id: true,
name: true,
},
},
},
});
let nextCursor: typeof cursor | undefined = undefined;
if (polls.length > input.limit) {
const nextItem = polls.pop();
nextCursor = nextItem!.id;
}
return {
polls,
nextCursor,
};
}),
// START LEGACY ROUTES
create: possiblyPublicProcedure

View file

@ -7,13 +7,9 @@ const prisma = new PrismaClient();
const randInt = (max = 1, floor = 0) => {
return Math.round(Math.random() * max) + floor;
};
async function createPollsForUser(userId: string) {
// Create some polls
const polls = await Promise.all(
Array.from({ length: 5 }).map(async (_, i) => {
async function createPollForUser(userId: string) {
// create some polls with no duration (all day) and some with a random duration.
const duration = i % 2 === 0 ? 60 * randInt(8, 1) : 0;
const duration = 60 * randInt(8);
let cursor = dayjs().add(randInt(30), "day").second(0).minute(0);
const numberOfOptions = randInt(30, 2);
@ -61,8 +57,14 @@ async function createPollsForUser(userId: string) {
participantUrlId: faker.random.alpha(10),
},
});
return poll;
}),
}
async function createPollsForUser(userId: string) {
// Create some polls
const polls = await Promise.all(
Array.from({ length: 100 }).map(() => createPollForUser(userId)),
);
// Create some votes