Fix Infinite Load Data
Forum & User Search – User - app/(application)/(user)/forum/index.tsx - app/(application)/(user)/user-search/index.tsx Global & Core - app/+not-found.tsx - screens/RootLayout/AppRoot.tsx - constants/constans-value.ts Component - components/Image/AvatarComp.tsx API Client - service/api-client/api-user.ts Untracked Files - QWEN.md - helpers/ - hooks/use-pagination.tsx - screens/Forum/ViewBeranda3.tsx - screens/UserSeach/ ### No Issue
This commit is contained in:
184
hooks/use-pagination.tsx
Normal file
184
hooks/use-pagination.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
|
||||
interface UsePaginationProps<T> {
|
||||
/**
|
||||
* Fungsi API untuk fetch data
|
||||
* @param page - nomor halaman
|
||||
* @param search - query pencarian (opsional)
|
||||
* @returns Promise dengan response API (bukan langsung array)
|
||||
*/
|
||||
fetchFunction: (page: number, search?: string) => Promise<{ data: T[] }>;
|
||||
|
||||
/**
|
||||
* Jumlah data per halaman (harus sama dengan API)
|
||||
* @default 5
|
||||
*/
|
||||
pageSize?: number;
|
||||
|
||||
/**
|
||||
* Query pencarian
|
||||
*/
|
||||
searchQuery?: string;
|
||||
|
||||
/**
|
||||
* Dependencies tambahan untuk trigger reload
|
||||
* Contoh: [userId, categoryId]
|
||||
*/
|
||||
dependencies?: any[];
|
||||
|
||||
/**
|
||||
* Callback saat data berhasil di-fetch
|
||||
*/
|
||||
onDataFetched?: (data: T[]) => void;
|
||||
|
||||
/**
|
||||
* Callback saat terjadi error
|
||||
*/
|
||||
onError?: (error: any) => void;
|
||||
}
|
||||
|
||||
interface UsePaginationReturn<T> {
|
||||
// Data state
|
||||
listData: T[];
|
||||
loading: boolean;
|
||||
refreshing: boolean;
|
||||
hasMore: boolean;
|
||||
page: number;
|
||||
|
||||
// Actions
|
||||
onRefresh: () => void;
|
||||
loadMore: () => void;
|
||||
reset: () => void;
|
||||
setListData: React.Dispatch<React.SetStateAction<T[]>>;
|
||||
isInitialLoad: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Hook untuk menangani pagination dengan infinite scroll
|
||||
*
|
||||
* Hook ini mengembalikan props yang siap digunakan langsung dengan NewWrapper
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const pagination = usePagination({
|
||||
* fetchFunction: async (page, search) => {
|
||||
* return await apiForumGetAll({
|
||||
* category: "beranda",
|
||||
* search: search || "",
|
||||
* userLoginId: user.id,
|
||||
* page: String(page),
|
||||
* });
|
||||
* },
|
||||
* pageSize: 5,
|
||||
* searchQuery: search,
|
||||
* dependencies: [user?.id]
|
||||
* });
|
||||
*
|
||||
* // Lalu gunakan langsung di NewWrapper:
|
||||
* <NewWrapper
|
||||
* listData={pagination.listData}
|
||||
* refreshControl={<RefreshControl refreshing={pagination.refreshing} onRefresh={pagination.onRefresh} />}
|
||||
* onEndReached={pagination.loadMore}
|
||||
* // ... props lainnya
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export function usePagination<T = any>({
|
||||
fetchFunction,
|
||||
pageSize = 5,
|
||||
searchQuery = "",
|
||||
dependencies = [],
|
||||
onDataFetched,
|
||||
onError,
|
||||
}: UsePaginationProps<T>): UsePaginationReturn<T> {
|
||||
const [listData, setListData] = useState<T[]>([]);
|
||||
const [loading, setLoading] = useState(true); // Set true untuk initial load
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true); // Track initial load
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
/**
|
||||
* Fungsi utama untuk fetch data
|
||||
*/
|
||||
const fetchData = async (pageNumber: number, clear: boolean) => {
|
||||
// Cegah multiple call
|
||||
if (!clear && (loading || refreshing)) return;
|
||||
|
||||
const isRefresh = clear;
|
||||
if (isRefresh) setRefreshing(true);
|
||||
if (!isRefresh) setLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetchFunction(pageNumber, searchQuery);
|
||||
const newData = response.data || [];
|
||||
// console.log("newData", newData);
|
||||
setListData((prev) => {
|
||||
const current = Array.isArray(prev) ? prev : [];
|
||||
return clear ? newData : [...current, ...newData];
|
||||
});
|
||||
// setTimeout(() => {
|
||||
// }, 4000);
|
||||
|
||||
setHasMore(newData.length === pageSize);
|
||||
setPage(pageNumber);
|
||||
|
||||
// Callback jika ada
|
||||
onDataFetched?.(newData);
|
||||
} catch (error) {
|
||||
console.error("[usePagination] Error fetching data:", error);
|
||||
setHasMore(false);
|
||||
onError?.(error);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
setLoading(false);
|
||||
setIsInitialLoad(false); // Set false setelah initial load
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset dan reload saat search atau dependencies berubah
|
||||
*/
|
||||
useEffect(() => {
|
||||
reset();
|
||||
fetchData(1, true);
|
||||
}, [searchQuery, ...dependencies]);
|
||||
|
||||
/**
|
||||
* Pull-to-refresh
|
||||
*/
|
||||
const onRefresh = useCallback(() => {
|
||||
fetchData(1, true);
|
||||
}, [searchQuery, ...dependencies]);
|
||||
|
||||
/**
|
||||
* Load more (infinite scroll)
|
||||
*/
|
||||
const loadMore = useCallback(() => {
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
fetchData(page + 1, false);
|
||||
}
|
||||
}, [hasMore, loading, refreshing, page, searchQuery, ...dependencies]);
|
||||
|
||||
/**
|
||||
* Reset state pagination
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
setPage(1);
|
||||
setListData([]);
|
||||
setHasMore(true);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
listData,
|
||||
loading,
|
||||
refreshing,
|
||||
hasMore,
|
||||
page,
|
||||
onRefresh,
|
||||
loadMore,
|
||||
reset,
|
||||
setListData,
|
||||
isInitialLoad
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user