/** * usePagination Hook * * Generic reusable pagination hook for managing page state and navigation. * Provides page number, page size, skip offset calculation, and navigation handlers. * * @layer Presentation */ import { useState, useCallback, useMemo } from 'react'; /** * Pagination Hook Options */ export interface UsePaginationOptions { /** Initial page number (default: 1) */ initialPage?: number; /** Initial page size (default: 20) */ initialPageSize?: number; /** Default page size for reset (default: 20) */ defaultPageSize?: number; } /** * Pagination Hook Return Type */ export interface UsePaginationReturn { /** Current page number (1-indexed) */ currentPage: number; /** Number of items per page */ pageSize: number; /** Skip offset for API (0-indexed, calculated as (page - 1) * pageSize) */ skip: number; /** Navigate to specific page */ setPage: (page: number) => void; /** Change page size (resets to page 1) */ setPageSize: (size: number) => void; /** Reset pagination to initial state */ resetPagination: () => void; /** Navigate to next page */ nextPage: () => void; /** Navigate to previous page */ prevPage: () => void; /** Navigate to first page */ firstPage: () => void; /** Navigate to last page (requires total items) */ lastPage: (totalItems: number) => void; /** Check if on first page */ isFirstPage: boolean; /** Check if on last page (requires total items) */ isLastPage: (totalItems: number) => boolean; } /** * usePagination Hook * * Manages pagination state for tables and lists. Handles page navigation, * page size changes, and calculates skip offset for skip-based API pagination. * * @param options - Pagination configuration options * @returns Pagination state and navigation functions * * @example * ```tsx * function DataTable() { * const pagination = usePagination({ defaultPageSize: 20 }); * * const { data } = useQuery({ * queryKey: ['items', pagination.skip, pagination.pageSize], * queryFn: () => fetchItems({ * skip: pagination.skip, * limit: pagination.pageSize, * }), * }); * * return ( *
* * * * ); * } * ``` */ export function usePagination(options: UsePaginationOptions = {}): UsePaginationReturn { const { initialPage = 1, initialPageSize = 20, defaultPageSize = 20, } = options; // State const [currentPage, setCurrentPage] = useState(initialPage); const [pageSize, setPageSizeState] = useState(initialPageSize); /** * Calculate skip offset for API * Skip is 0-indexed: skip = (page - 1) * pageSize */ const skip = useMemo(() => { return (currentPage - 1) * pageSize; }, [currentPage, pageSize]); /** * Navigate to specific page */ const setPage = useCallback((page: number) => { if (page < 1) { console.warn('[usePagination] Page number must be >= 1, clamping to 1'); setCurrentPage(1); return; } setCurrentPage(page); }, []); /** * Change page size and reset to page 1 * (changing page size usually requires going back to start) */ const setPageSize = useCallback((size: number) => { if (size < 1) { console.warn('[usePagination] Page size must be >= 1, clamping to 1'); setPageSizeState(1); setCurrentPage(1); return; } setPageSizeState(size); setCurrentPage(1); // Reset to first page }, []); /** * Reset pagination to initial state */ const resetPagination = useCallback(() => { setCurrentPage(initialPage); setPageSizeState(defaultPageSize); }, [initialPage, defaultPageSize]); /** * Navigate to next page */ const nextPage = useCallback(() => { setCurrentPage((prev) => prev + 1); }, []); /** * Navigate to previous page (min page 1) */ const prevPage = useCallback(() => { setCurrentPage((prev) => Math.max(1, prev - 1)); }, []); /** * Navigate to first page */ const firstPage = useCallback(() => { setCurrentPage(1); }, []); /** * Navigate to last page * @param totalItems - Total number of items */ const lastPage = useCallback((totalItems: number) => { const lastPageNum = Math.ceil(totalItems / pageSize); setCurrentPage(lastPageNum > 0 ? lastPageNum : 1); }, [pageSize]); /** * Check if on first page */ const isFirstPage = currentPage === 1; /** * Check if on last page * @param totalItems - Total number of items */ const isLastPage = useCallback((totalItems: number) => { const lastPageNum = Math.ceil(totalItems / pageSize); return currentPage >= lastPageNum; }, [currentPage, pageSize]); return { currentPage, pageSize, skip, setPage, setPageSize, resetPagination, nextPage, prevPage, firstPage, lastPage, isFirstPage, isLastPage, }; }