Files
buzz-sheet/components/comments/use-comment-feed.ts
T
gunshiz 5812d99715
CI / Verify (push) Successful in 2m30s
CI / Build immutable images and deploy (push) Successful in 3m9s
feat : comment feature
2026-10-08 01:40:08 +07:00

71 lines
3.5 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { CommentPage } from "@/lib/comments/types";
import { commentRequest } from "./client";
export function useCommentFeed(endpoint: string, initial?: CommentPage) {
const [page, setPage] = useState<CommentPage>(initial ?? { items: [], nextCursor: null, viewer: null });
const [dataEndpoint, setDataEndpoint] = useState(endpoint);
const [busy, setBusy] = useState(!initial);
const [error, setError] = useState("");
const [revision, setRevision] = useState(0);
const generation = useRef(0);
const loadedPages = useRef(1);
const mountedEndpoint = useRef(initial ? endpoint : "");
const active = useRef<AbortController | null>(null);
const lastPayload = useRef(JSON.stringify(initial ?? { items: [], nextCursor: null, viewer: null }));
const refresh = useCallback(async (options: { silent?: boolean; forceRevision?: boolean } = {}) => {
active.current?.abort();
const controller = new AbortController();
active.current = controller;
const sequence = ++generation.current;
if (options.silent === false) setBusy(true);
try {
let result = await commentRequest<CommentPage>(endpoint, { signal: controller.signal });
for (let i = 1; i < loadedPages.current && result.nextCursor; i++) {
const next = await commentRequest<CommentPage>(`${endpoint}&cursor=${encodeURIComponent(result.nextCursor)}`, { signal: controller.signal });
result = { ...next, items: [...result.items, ...next.items] };
}
if (sequence === generation.current) {
const payload = JSON.stringify(result);
if (payload !== lastPayload.current) {
lastPayload.current = payload;
setPage(result); setRevision((value) => value + 1);
} else if (options.forceRevision) {
setRevision((value) => value + 1);
}
setDataEndpoint(endpoint); setError("");
}
} catch (cause) {
if (!controller.signal.aborted && sequence === generation.current) setError(cause instanceof Error ? cause.message : "ไม่สามารถโหลดความคิดเห็นได้");
} finally { if (sequence === generation.current) setBusy(false); }
}, [endpoint]);
const more = useCallback(async () => {
if (!page.nextCursor || busy) return;
const sequence = ++generation.current;
setBusy(true);
try {
const next = await commentRequest<CommentPage>(`${endpoint}&cursor=${encodeURIComponent(page.nextCursor)}`);
if (sequence === generation.current) {
const result = { ...next, items: [...page.items, ...next.items.filter((item) => !page.items.some((old) => old.id === item.id))] };
lastPayload.current = JSON.stringify(result);
setPage(result);
loadedPages.current += 1;
setError("");
}
} catch (cause) { if (sequence === generation.current) setError(cause instanceof Error ? cause.message : "ไม่สามารถโหลดความคิดเห็นได้"); }
finally { if (sequence === generation.current) setBusy(false); }
}, [endpoint, page, busy]);
useEffect(() => {
if (mountedEndpoint.current !== endpoint) {
loadedPages.current = 1;
mountedEndpoint.current = endpoint;
void refresh({ silent: false });
}
return () => { active.current?.abort(); };
}, [endpoint, refresh]);
const visiblePage = dataEndpoint === endpoint ? page : { items: [], nextCursor: null, viewer: page.viewer };
return { page: visiblePage, busy, error, revision, refresh, more };
}