Skip to content

Instantly share code, notes, and snippets.

@rootasjey
Created November 30, 2021 23:06
Show Gist options
  • Save rootasjey/1f3e273b5b5ae45a29f8c0f7dc9fb1f8 to your computer and use it in GitHub Desktop.
Save rootasjey/1f3e273b5b5ae45a29f8c0f7dc9fb1f8 to your computer and use it in GitHub Desktop.
React pagination component (TypeScript)
import { ReactElement, useState } from 'react'
import classNames from 'classnames'
import Image from 'next/image'
import ArrowRightIcon from '@/public/images/homepage/arrow-right.svg'
type PaginationProps = {
/** Number of pages to display in the pagination. */
pagesCount: number
/** Fired when the user clicks on a page number. */
onChangePage?: (pageNumber: number) => void
/** Fired when th user clicks on the next page button */
onNextPage?: (pageNumber: number) => void
}
/**
* Range of displayed numbers.
* For example, if this constant is equal to 2,
* a possible configuration is: [1 ... 2, 3 ... 6].
* If `DYNAMIC_RANGE = 3`, it'll be: [1 ... 2, 3, 4 ... 6].
**/
const DYNAMIC_RANGE = 2
export const Pagination = ({
onChangePage,
onNextPage,
pagesCount = 0,
}: PaginationProps): ReactElement => {
const [selectedIndex, setSelectedIndex] = useState(1)
const [startOffset, setStartOffset] = useState(1)
const numbers = Array.from(Array(pagesCount).keys()).map((index) => index + 1)
const displayedNumbers: Array<number | string> = Array.from(numbers)
if (numbers.length > 4) {
displayedNumbers.push(1)
const portion = numbers.slice(startOffset, startOffset + DYNAMIC_RANGE)
if (portion[0] - 1 !== 1) {
displayedNumbers.push('...')
}
displayedNumbers.push(...portion)
const lastNumber = numbers[numbers.length - 1]
if (lastNumber !== portion[portion.length - 1]) {
if (portion[portion.length - 1] + 1 !== lastNumber) {
displayedNumbers.push('...')
}
displayedNumbers.push(lastNumber)
}
}
const onClickNextPage = (): void => {
if (onNextPage) {
const nextPageNumber = selectedIndex + 1
setSelectedIndex(nextPageNumber)
onNextPage(nextPageNumber)
tryUpdateOffset(nextPageNumber)
}
}
const onClickNumber = (key: number | string): void => {
if (typeof key === 'string') {
return
}
if (onChangePage) {
setSelectedIndex(key)
onChangePage(key)
}
tryUpdateOffset(key)
}
const tryUpdateOffset = (key: number): void => {
const isEndReached = numbers.length - key < 2
if (key === startOffset + DYNAMIC_RANGE && !isEndReached) {
setStartOffset(startOffset + 1)
return
} else if (key - 1 === startOffset && startOffset < selectedIndex) {
let newOffset = startOffset - 1
newOffset = newOffset < 1 ? 1 : newOffset
setStartOffset(newOffset)
}
if (key === 1) {
setStartOffset(1)
return
}
if (key === numbers.length) {
setStartOffset(numbers.length - DYNAMIC_RANGE - 1)
return
}
}
return (
<div
className={classNames('flex flex-row justify-center gap-4', {
hidden: displayedNumbers.length < 2,
})}
>
{displayedNumbers.map((key) => {
const editedKey = key === '...' ? Math.random() : key
return (
<button
key={editedKey}
className={classNames('px-4 py-2 shadow-md rounded', {
'text-blue-turquoise border-solid border-2 border-blue-turquoise':
key === selectedIndex,
})}
onClick={() => onClickNumber(key)}
>
{key}
</button>
)
})}
<button onClick={onClickNextPage}>
<Image src={ArrowRightIcon} width={14} height={14} alt="arrow right icon" />
</button>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment