Skip to content

Commit

Permalink
refactor: Extract pagination to usePagination hook
Browse files Browse the repository at this point in the history
  • Loading branch information
karelianpie committed Feb 28, 2023
1 parent ee880b4 commit 11b90cd
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 58 deletions.
69 changes: 11 additions & 58 deletions apps/common/components/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useCallback, useMemo, useState} from 'react';
import ReactPaginate from 'react-paginate';
import {sort} from '@veYFI/utils';
import IconChevronPlain from '@common/icons/IconChevronPlain';
import IconPaginationArrow from '@common/icons/IconPaginationArrow';

import {usePagination} from './usePagination';

import type {ReactElement} from 'react';

Expand All @@ -25,7 +25,7 @@ type TMetadata<T> = {
format?: (item: T) => string;
transform?: (item: T) => ReactElement;
}

type TTableProps<T> = {
metadata: TMetadata<T>[];
data: T[];
Expand All @@ -35,30 +35,22 @@ type TTableProps<T> = {
}

function Table<T>({metadata, data, columns, initialSortBy, onRowClick}: TTableProps<T>): ReactElement {
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});
const [itemOffset, set_itemOffset] = useState(0);
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});

const sortedData = useMemo((): T[] => {
return sortedBy && order ? sort(data, sortedBy, order) : data;
}, [data, order, sortedBy]);

const {currentItems, PaginationElement} = usePagination<T>({data: sortedData, itemsPerPage: 10});

const handleSort = useCallback((key: Extract<keyof T, string>): void => {
const willChangeSortKey = sortedBy !== key;
const newOrder = switchOrder(willChangeSortKey ? 'asc' : order);
set_state({sortedBy: newOrder ? key : undefined, order: newOrder});
}, [order, sortedBy]);

const sortedData = useMemo((): T[] => {
return sortedBy && order ? sort(data, sortedBy, order) : data;
}, [data, order, sortedBy]);

const numberOfColumns = Math.min(columns ?? (metadata.length), 12).toString();

const ITEMS_PER_PAGE = 10;
const endOffset = itemOffset + ITEMS_PER_PAGE;
const currentItems = sortedData.slice(itemOffset, endOffset);
const pageCount = Math.ceil(sortedData.length / ITEMS_PER_PAGE);
const handlePageClick = (event: {selected: number}): void => {
const newOffset = (event.selected * ITEMS_PER_PAGE) % sortedData.length;
set_itemOffset(newOffset);
};

return (
<div className={'w-full'}>
<div className={`mb-2 hidden w-full px-6 md:grid md:grid-flow-col ${`md:grid-cols-${numberOfColumns}`}`}>
Expand Down Expand Up @@ -102,46 +94,7 @@ function Table<T>({metadata, data, columns, initialSortBy, onRowClick}: TTablePr
})}
<div className={'mt-4'}>
<div className={'border-t border-neutral-300 p-4 pb-0'}>
<div className={'flex flex-1 justify-between sm:hidden'}>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Previous'}
</a>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative ml-3 inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Next'}
</a>
</div>
<div className={'sm-border hidden sm:flex sm:items-center sm:justify-center'}>
<div className={'ml-3 flex-1'}>
<p className={'text-sm text-[#5B5B5B]'}>
{'Showing '}<span className={'font-medium'}>{endOffset - (ITEMS_PER_PAGE - 1)}</span>{' to '}<span className={'font-medium'}>{Math.min(endOffset, sortedData.length)}</span>{' of'}{' '}
<span className={'font-medium'}>{sortedData.length}</span> {'results'}
</p>
</div>
<ReactPaginate
className={'inline-flex align-middle'}
pageLinkClassName={'text-[#5B5B5B] hover:border-b-2 inline-flex items-end mx-1.5 mt-2.5 px-0.5 text-xs'}
previousLinkClassName={'inline-flex items-center m-2 font-medium'}
nextLinkClassName={'inline-flex items-center m-2 font-medium'}
breakLinkClassName={'text-[#5B5B5B] inline-flex items-center mx-2 my-2 px-0.5 font-medium'}
activeLinkClassName={'text-gray-900 font-bold border-b-2 items-center mx-2 my-2 px-0.5 md:inline-flex'}
disabledLinkClassName={'cursor-not-allowed hover:bg-neutral-100'}
disabledClassName={'text-neutral-300'}
renderOnZeroPageCount={(): null => null}
breakLabel={'...'}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<IconPaginationArrow className={'h-5 w-5 transition-transform'} />}
nextLabel={<IconPaginationArrow className={'h-5 w-5 -rotate-180 transition-transform'} />}
/>
<div className={'sm:flex-1'}></div>
</div>
{PaginationElement}
</div>
</div>
</div>
Expand Down
73 changes: 73 additions & 0 deletions apps/common/components/usePagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {useState} from 'react';
import ReactPaginate from 'react-paginate';
import IconPaginationArrow from '@common/icons/IconPaginationArrow';

import type {ReactElement} from 'react';

type TProps<T> = {
data: T[];
itemsPerPage: number;
}

export function usePagination<T>({data, itemsPerPage}: TProps<T>): { PaginationElement: ReactElement; currentItems: T[]} {
const [itemOffset, set_itemOffset] = useState(0);

const endOffset = itemOffset + itemsPerPage;

const pageCount = Math.ceil(data.length / itemsPerPage);

const currentItems = data.slice(itemOffset, endOffset);

const handlePageClick = (event: {selected: number}): void => {
const newOffset = (event.selected * itemsPerPage) % data.length;
set_itemOffset(newOffset);
};

return {
currentItems,
PaginationElement: (
<>
<div className={'flex flex-1 justify-between sm:hidden'}>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Previous'}
</a>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative ml-3 inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Next'}
</a>
</div>
<div className={'sm-border hidden sm:flex sm:items-center sm:justify-center'}>
<div className={'ml-3 flex-1'}>
<p className={'text-sm text-[#5B5B5B]'}>
{'Showing '}<span className={'font-medium'}>{endOffset - (itemsPerPage - 1)}</span>{' to '}<span className={'font-medium'}>{Math.min(endOffset, data.length)}</span>{' of'}{' '}
<span className={'font-medium'}>{data.length}</span> {'results'}
</p>
</div>
<ReactPaginate
className={'inline-flex align-middle'}
pageLinkClassName={'text-[#5B5B5B] hover:border-b-2 inline-flex items-end mx-1.5 mt-2.5 px-0.5 text-xs'}
previousLinkClassName={'inline-flex items-center m-2 font-medium'}
nextLinkClassName={'inline-flex items-center m-2 font-medium'}
breakLinkClassName={'text-[#5B5B5B] inline-flex items-center mx-2 my-2 px-0.5 font-medium'}
activeLinkClassName={'text-gray-900 font-bold border-b-2 items-center mx-2 my-2 px-0.5 md:inline-flex'}
disabledLinkClassName={'cursor-not-allowed hover:bg-neutral-100'}
disabledClassName={'text-neutral-300'}
renderOnZeroPageCount={(): null => null}
breakLabel={'...'}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<IconPaginationArrow className={'h-5 w-5 transition-transform'} />}
nextLabel={<IconPaginationArrow className={'h-5 w-5 -rotate-180 transition-transform'} />}
/>
<div className={'sm:flex-1'}></div>
</div>
</>
)
};
}

0 comments on commit 11b90cd

Please sign in to comment.