mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 10:03:14 +00:00
Keychain token storage, Axios client with async token injection, Cognito PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query config, offline draft queue with NetInfo auto-submit, and SHOC theme.
73 lines
2 KiB
TypeScript
73 lines
2 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { DEBOUNCE_SEARCH, DEFAULT_PAGE, DEFAULT_PAGE_SIZE } from '../constants';
|
|
|
|
interface PaginatedResult<T> {
|
|
items: T[];
|
|
totalCount: number;
|
|
}
|
|
|
|
type FetchFn<T> = (
|
|
params: Record<string, unknown>,
|
|
) => Promise<PaginatedResult<T>>;
|
|
|
|
export function usePaginatedList<T>(
|
|
fetchFn: FetchFn<T>,
|
|
extraParams: Record<string, unknown> = {},
|
|
) {
|
|
const [rows, setRows] = useState<T[]>([]);
|
|
const [search, setSearch] = useState('');
|
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
const [page, setPage] = useState(DEFAULT_PAGE);
|
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
const [totalCount, setTotalCount] = useState(0);
|
|
const [loading, setLoading] = useState(true);
|
|
const [err, setErr] = useState('');
|
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
const extraKey = JSON.stringify(extraParams);
|
|
|
|
useEffect(() => {
|
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
debounceRef.current = setTimeout(() => {
|
|
setDebouncedSearch(search);
|
|
setPage(1);
|
|
}, DEBOUNCE_SEARCH);
|
|
return () => {
|
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
};
|
|
}, [search]);
|
|
|
|
const reload = useCallback(() => {
|
|
setLoading(true);
|
|
setErr('');
|
|
fetchFn({ search: debouncedSearch, page, pageSize, ...extraParams })
|
|
.then(({ items, totalCount: total }) => {
|
|
setRows(items);
|
|
setTotalCount(total);
|
|
})
|
|
.catch((e: Error) => setErr(e.message || 'Failed to load'))
|
|
.finally(() => setLoading(false));
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [fetchFn, debouncedSearch, page, pageSize, extraKey]);
|
|
|
|
useEffect(() => {
|
|
reload();
|
|
}, [reload]);
|
|
|
|
return useMemo(
|
|
() => ({
|
|
rows,
|
|
search,
|
|
setSearch,
|
|
page,
|
|
setPage,
|
|
pageSize,
|
|
setPageSize,
|
|
totalCount,
|
|
loading,
|
|
err,
|
|
reload,
|
|
}),
|
|
[rows, search, page, pageSize, totalCount, loading, err, reload],
|
|
);
|
|
}
|