import { useReducer } from 'react'; interface Page { page: number; current: boolean; } interface IUsePagination { items: T[]; changeOffset: (offset: Offset, newValue?: number) => void; /** * Returns an array with the aviables pages to directly navigate. * This pages are centered in the current page and offest to 5 items to each side. * @returns {Page} */ getPaginationRange: () => Page[]; } interface Props { items: T[]; limit: number; } interface State { offset: number; total: number; limit: number; } type ActionType = | { type: 'update'; value: any; name: string } | { type: 'update'; value: any; name: string }; export enum Offset { Next, Prev, First, Last, To, } export default function usePagination({ items, limit = 30, }: Props): IUsePagination { const reducer = (state: State, action: ActionType): State => { switch (action.type) { case 'update': return { ...state, [action.name]: action.value, }; default: return state; } }; const [state, dispatch] = useReducer(reducer, { offset: 0, total: 0, limit, }); const changeOffset = (offset: Offset, newValue: number = 1): void => { let value = 0; switch (offset) { case Offset.Next: value = state.offset + state.limit; break; case Offset.Prev: value = state.offset - state.limit; break; case Offset.First: value = 0; break; case Offset.Last: value = items.length - state.limit; break; case Offset.To: value = (newValue - 1) * state.limit; break; default: break; } if (value < 0 || value > items.length - state.limit) { return; } dispatch({ type: 'update', name: 'offset', value, }); }; function getPaginationRange(): Page[] { // NOTE: this is made to work with uneven numbers, // So the current page is always aligned in the center. const paginationToSides = 5; const currentPage = Math.ceil(state.offset / state.limit) + 1; const lastPage = Math.ceil(items.length / state.limit); const start = Math.max(currentPage - paginationToSides - 1, 0); const end = Math.min(currentPage + paginationToSides, lastPage); return Array.from( { length: end - start, }, (_, idx) => { const page = idx + 1 + start; return { page, current: page === currentPage }; }, ); } return { changeOffset, getPaginationRange, items: items.slice(state.offset, state.offset + state.limit), }; }