aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src/hooks/usePaginatedArticles.ts
blob: 5ddf888ac79d0cdbb2921e49147e02aae07ad315 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import { useCallback, useEffect, useState } from "react";
import type { components } from "../api/generated";
import { api } from "../services/api-client";

export type ArticleType = components["schemas"]["Article"];

interface UsePaginatedArticlesOptions {
	isReadView: boolean;
	feedId: string | null;
}

interface UsePaginatedArticlesResult {
	articles: ArticleType[];
	hasNextPage: boolean;
	loading: boolean;
	loadingMore: boolean;
	loadMore: () => void;
	error: Error | null;
}

export function usePaginatedArticles({
	isReadView,
	feedId,
}: UsePaginatedArticlesOptions): UsePaginatedArticlesResult {
	const [articles, setArticles] = useState<ArticleType[]>([]);
	const [hasNextPage, setHasNextPage] = useState(false);
	const [endCursor, setEndCursor] = useState<string | null>(null);
	const [loading, setLoading] = useState(true);
	const [loadingMore, setLoadingMore] = useState(false);
	const [error, setError] = useState<Error | null>(null);

	const fetchArticles = useCallback(
		async (after: string | null, append: boolean) => {
			const query: { feedId?: string; after?: string } = {};
			if (feedId) query.feedId = feedId;
			if (after) query.after = after;

			const endpoint = isReadView
				? "/api/articles/read"
				: "/api/articles/unread";

			const { data } = await api.GET(endpoint, {
				params: { query },
			});

			if (!data) {
				setError(new Error("Failed to fetch articles"));
				return;
			}

			if (data) {
				setArticles((prev) =>
					append ? [...prev, ...data.articles] : data.articles,
				);
				setHasNextPage(data.pageInfo.hasNextPage);
				setEndCursor(data.pageInfo.endCursor ?? null);
				setError(null);
			}
		},
		[isReadView, feedId],
	);

	// Reset and fetch on feedId or view change
	useEffect(() => {
		setArticles([]);
		setEndCursor(null);
		setHasNextPage(false);
		setLoading(true);
		setError(null);
		fetchArticles(null, false).finally(() => setLoading(false));
	}, [fetchArticles]);

	const loadMore = useCallback(() => {
		if (!hasNextPage || loadingMore) return;
		setLoadingMore(true);
		fetchArticles(endCursor, true).finally(() => setLoadingMore(false));
	}, [fetchArticles, endCursor, hasNextPage, loadingMore]);

	return { articles, hasNextPage, loading, loadingMore, loadMore, error };
}