Files
spsays-admin/lib/use-paged-list.ts
T
2026-06-16 14:50:09 +05:30

77 lines
2.1 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import type { Paged } from "@/lib/api";
const PAGE_SIZE = 25;
export type PagedListState<T> = {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
loading: boolean;
error: string | null;
setPage: (p: number) => void;
refresh: () => void;
};
/**
* Shared search + offset-pagination logic for the admin list pages. `fetcher`
* receives (limit, offset) plus whatever extra deps the caller closes over
* (search query, status filter, report type); pass those in `deps` so a change
* resets to page 1 and refetches. A request token guards against out-of-order
* responses when the user types/pages quickly.
*/
export function usePagedList<T>(
fetcher: (limit: number, offset: number) => Promise<Paged<T>>,
deps: unknown[],
pageSize = PAGE_SIZE,
): PagedListState<T> {
const [items, setItems] = useState<T[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const token = useRef(0);
// Reset to page 1 whenever the external filters change.
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setPage(1), deps);
const load = useCallback(() => {
const my = ++token.current;
setLoading(true);
setError(null);
fetcher(pageSize, (page - 1) * pageSize)
.then((res) => {
if (my !== token.current) return;
setItems(res.items);
setTotal(res.total);
})
.catch((e) => {
if (my !== token.current) return;
setError(e instanceof Error ? e.message : "Could not load.");
})
.finally(() => {
if (my === token.current) setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, pageSize, ...deps]);
useEffect(() => load(), [load]);
return {
items,
total,
page,
pageSize,
totalPages: Math.max(1, Math.ceil(total / pageSize)),
loading,
error,
setPage,
refresh: load,
};
}