71 lines
3.5 KiB
TypeScript
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 };
|
|
}
|