fix(comments): list all memo comments via pagination

Memo detail only showed the first 10 comments: the frontend requested
pageSize=0, which the backend normalizes to DefaultPageSize (10), and the
returned nextPageToken was never followed.

Add useInfiniteMemoComments (mirrors useInfiniteMemos) so the detail page
paginates through every comment, with a "Load more" control in
MemoCommentSection. Page size defaults to DEFAULT_LIST_MEMOS_PAGE_SIZE to
match the memo list convention.
pull/6027/head
johnnyjoygh 2 weeks ago
parent f5a60263b2
commit f727ad217c

@ -1,4 +1,4 @@
import { MessageCircleIcon } from "lucide-react";
import { LoaderCircleIcon, MessageCircleIcon } from "lucide-react";
import { useState } from "react";
import MemoEditor from "@/components/MemoEditor";
import MemoView from "@/components/MemoView";
@ -12,9 +12,12 @@ interface Props {
memo: Memo;
comments: Memo[];
parentPage?: string;
hasMoreComments?: boolean;
isFetchingMoreComments?: boolean;
onLoadMoreComments?: () => void;
}
const MemoCommentSection = ({ memo, comments, parentPage }: Props) => {
const MemoCommentSection = ({ memo, comments, parentPage, hasMoreComments, isFetchingMoreComments, onLoadMoreComments }: Props) => {
const t = useTranslate();
const currentUser = useCurrentUser();
const [showEditor, setShowEditor] = useState(false);
@ -71,6 +74,14 @@ const MemoCommentSection = ({ memo, comments, parentPage }: Props) => {
<MemoView memo={comment} parentPage={parentPage} showCreator compact />
</div>
))}
{hasMoreComments && (
<div className="w-full mt-4 flex justify-center">
<Button variant="outline" className="rounded-full px-4" onClick={onLoadMoreComments} disabled={isFetchingMoreComments}>
{isFetchingMoreComments && <LoaderCircleIcon className="h-4 w-4 animate-spin" />}
{t(isFetchingMoreComments ? "resource.fetching-data" : "memo.load-more")}
</Button>
</div>
)}
</div>
</div>
);

@ -3,6 +3,7 @@ import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
import type { InfiniteData } from "@tanstack/react-query";
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { memoServiceClient } from "@/connect";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import { userKeys } from "@/hooks/useUserQueries";
import type { ListMemosRequest, ListMemosResponse, Memo } from "@/types/proto/api/v1/memo_service_pb";
import { ListMemoCommentsRequestSchema, ListMemosRequestSchema, MemoSchema } from "@/types/proto/api/v1/memo_service_pb";
@ -284,3 +285,27 @@ export function useMemoComments(name: string, options?: { enabled?: boolean; pag
staleTime: 1000 * 60, // 1 minute
});
}
// useInfiniteMemoComments paginates through every comment via nextPageToken, instead of
// stopping at the server's default page size (the cause of comments being truncated to 10).
export function useInfiniteMemoComments(name: string, options?: { enabled?: boolean; pageSize?: number }) {
const pageSize = options?.pageSize ?? DEFAULT_LIST_MEMOS_PAGE_SIZE;
return useInfiniteQuery({
queryKey: [...memoKeys.comments(name), "infinite", pageSize],
queryFn: async ({ pageParam }) => {
const response = await memoServiceClient.listMemoComments(
create(ListMemoCommentsRequestSchema, {
name,
pageSize,
pageToken: pageParam || "",
}),
);
return response;
},
initialPageParam: "",
getNextPageParam: (lastPage) => lastPage.nextPageToken || undefined,
select: (data) => data.pages.flatMap((page) => page.memos),
enabled: options?.enabled ?? true,
staleTime: 1000 * 60, // 1 minute
});
}

@ -10,7 +10,7 @@ import MobileHeader from "@/components/MobileHeader";
import { memoNamePrefix } from "@/helpers/resource-names";
import useMediaQuery from "@/hooks/useMediaQuery";
import useMemoDetailError from "@/hooks/useMemoDetailError";
import { useMemo, useMemoComments } from "@/hooks/useMemoQueries";
import { useInfiniteMemoComments, useMemo } from "@/hooks/useMemoQueries";
import { useSharedMemo, withShareAttachmentLinks } from "@/hooks/useMemoShareQueries";
import { cn } from "@/lib/utils";
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
@ -48,10 +48,14 @@ const MemoDetail = () => {
enabled: !!memo?.parent,
});
const { data: commentsResponse } = useMemoComments(memoName, {
const {
data: comments = [],
fetchNextPage: fetchNextComments,
hasNextPage: hasNextComments,
isFetchingNextPage: isFetchingNextComments,
} = useInfiniteMemoComments(memoName, {
enabled: !!memo,
});
const comments = commentsResponse?.memos || [];
useEffect(() => {
if (!hash || comments.length === 0) return;
@ -110,7 +114,14 @@ const MemoDetail = () => {
showPinned
onShareImageDialogOpenChange={setShareImageDialogOpen}
/>
<MemoCommentSection memo={displayMemo} comments={comments} parentPage={locationState?.from} />
<MemoCommentSection
memo={displayMemo}
comments={comments}
parentPage={locationState?.from}
hasMoreComments={hasNextComments}
isFetchingMoreComments={isFetchingNextComments}
onLoadMoreComments={fetchNextComments}
/>
</div>
{md && (
<div className="sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full">

Loading…
Cancel
Save