Overview
The Pagination component provides page navigation controls for multi-page content. It includes previous/next buttons, individual page number links, and automatic URL parameter management.
Usage
import { Pagination } from '@/components'
import { useState } from 'react'
function JobSearchPage () {
const [ currentPage , setCurrentPage ] = useState ( 1 )
const handlePageChange = ( newPage ) => {
setCurrentPage ( newPage )
// Fetch jobs for the new page
}
return (
< Pagination
currentPage = { currentPage }
totalPages = { 10 }
onPageChange = { handlePageChange }
/>
)
}
Props
The currently active page number (1-based index)
The total number of pages available
Callback function called when the user changes pages. Receives the new page number as an argument. onPageChange : ( page : number ) => void
Features
Previous/Next Navigation
The component includes arrow buttons for navigating to the previous or next page:
const isFirstPage = currentPage === 1
const isLastPage = currentPage === totalPages
const stylePrevButton = isFirstPage ? { pointerEvents: 'none' , opacity: 0.5 } : {}
const styleNextButton = isLastPage ? { pointerEvents: 'none' , opacity: 0.5 } : {}
Buttons are automatically disabled when at the first or last page.
Direct Page Navigation
Users can click on any page number to jump directly to that page:
{ pages . map (( page ) => (
< a
key = { page }
data-page = { page }
href = "#"
className = { currentPage === page ? styles . isActive : '' }
onClick = { handleChangePage }
>
{ page }
</ a >
))}
URL Parameter Management
The component updates the URL with the current page:
const buildPageUrl = ( page ) => {
const url = new URL ( window . location )
url . searchParams . set ( 'page' , page )
return ` ${ url . pathname } ? ${ url . searchParams . toString () } `
}
This allows users to bookmark specific pages and use browser back/forward buttons.
Component Structure
Full Component
Basic Usage
With State Management
import styles from './Pagination.module.css'
export function Pagination ({ currentPage = 1 , totalPages = 10 , onPageChange }) {
const pages = Array . from ({ length: totalPages }, ( _ , i ) => i + 1 )
const isFirstPage = currentPage === 1
const isLastPage = currentPage === totalPages
const stylePrevButton = isFirstPage ? { pointerEvents: 'none' , opacity: 0.5 } : {}
const styleNextButton = isLastPage ? { pointerEvents: 'none' , opacity: 0.5 } : {}
const handlePrevClick = ( event ) => {
event . preventDefault ()
if ( isFirstPage === false ) {
onPageChange ( currentPage - 1 )
}
}
const handleNextClick = ( event ) => {
event . preventDefault ()
if ( isLastPage === false ) {
onPageChange ( currentPage + 1 )
}
}
const handleChangePage = ( event ) => {
event . preventDefault ()
const page = Number ( event . target . dataset . page )
if ( page !== currentPage ) {
onPageChange ( page )
}
}
const buildPageUrl = ( page ) => {
const url = new URL ( window . location )
url . searchParams . set ( 'page' , page )
return ` ${ url . pathname } ? ${ url . searchParams . toString () } `
}
return (
< nav className = { styles . pagination } >
< a href = { buildPageUrl ( currentPage - 1 ) } style = { stylePrevButton } onClick = { handlePrevClick } >
< svg > { /* Previous arrow icon */ } </ svg >
</ a >
{ pages . map (( page ) => (
< a
key = { page }
data-page = { page }
href = "#"
className = { currentPage === page ? styles . isActive : '' }
onClick = { handleChangePage }
>
{ page }
</ a >
)) }
< a href = { buildPageUrl ( currentPage + 1 ) } style = { styleNextButton } onClick = { handleNextClick } >
< svg > { /* Next arrow icon */ } </ svg >
</ a >
</ nav >
)
}
Styling
The component uses CSS modules (Pagination.module.css) for styling:
styles.pagination - Main navigation container
styles.isActive - Applied to the current page number
Disabled State Styling
Disabled buttons use inline styles:
{ pointerEvents : 'none' , opacity : 0.5 }
Event Handling
Page Change Handler
The onPageChange callback is called with the new page number:
const handlePageChange = ( newPage ) => {
console . log ( 'Navigating to page:' , newPage )
// Update state
setCurrentPage ( newPage )
// Fetch new data
fetchJobs ( newPage )
// Scroll to top
window . scrollTo ({ top: 0 , behavior: 'smooth' })
}
Preventing Default
All click handlers prevent the default link behavior:
const handlePrevClick = ( event ) => {
event . preventDefault ()
// ...
}
Usage Patterns
With URL Synchronization
import { Pagination } from '@/components'
import { useSearchParams } from 'react-router-dom'
function SearchPage () {
const [ searchParams , setSearchParams ] = useSearchParams ()
const currentPage = Number ( searchParams . get ( 'page' )) || 1
const handlePageChange = ( page ) => {
setSearchParams ({ page: page . toString () })
}
return (
< Pagination
currentPage = { currentPage }
totalPages = { 10 }
onPageChange = { handlePageChange }
/>
)
}
import { Pagination } from '@/components'
import { useState , useEffect } from 'react'
function ApiPaginatedList () {
const [ page , setPage ] = useState ( 1 )
const [ totalPages , setTotalPages ] = useState ( 0 )
const [ items , setItems ] = useState ([])
useEffect (() => {
fetch ( `/api/items?page= ${ page } ` )
. then ( res => res . json ())
. then ( data => {
setItems ( data . items )
setTotalPages ( data . totalPages )
})
}, [ page ])
return (
<>
< ItemList items = { items } />
< Pagination
currentPage = { page }
totalPages = { totalPages }
onPageChange = { setPage }
/>
</>
)
}
const handlePageChange = ( newPage ) => {
setCurrentPage ( newPage )
// Smooth scroll to top
window . scrollTo ({
top: 0 ,
behavior: 'smooth'
})
// Or scroll to specific element
document . querySelector ( '.jobs-listings' )?. scrollIntoView ({
behavior: 'smooth'
})
}
Accessibility
Uses semantic <nav> element for the pagination controls
Links are keyboard accessible
Disabled states use pointer-events: none and reduced opacity
Active page is visually indicated with the isActive class
Improvements
For better accessibility, consider:
< nav className = { styles . pagination } aria-label = "Pagination" >
< a
href = { buildPageUrl ( currentPage - 1 ) }
style = { stylePrevButton }
onClick = { handlePrevClick }
aria-label = "Previous page"
aria-disabled = { isFirstPage }
>
{ /* ... */ }
</ a >
{ pages . map (( page ) => (
< a
key = { page }
aria-label = { `Page ${ page } ` }
aria-current = { currentPage === page ? 'page' : undefined }
>
{ page }
</ a >
)) }
</ nav >
Large Page Counts
For pagination with many pages (e.g., 100+), consider implementing ellipsis:
function getVisiblePages ( current , total ) {
if ( total <= 7 ) return Array . from ({ length: total }, ( _ , i ) => i + 1 )
if ( current <= 4 ) return [ 1 , 2 , 3 , 4 , 5 , '...' , total ]
if ( current >= total - 3 ) return [ 1 , '...' , total - 4 , total - 3 , total - 2 , total - 1 , total ]
return [ 1 , '...' , current - 1 , current , current + 1 , '...' , total ]
}
Source Code
Location: src/components/Pagination/Pagination.jsx:3
JobListings Render paginated job listings
Components Overview View all available components
Best Practices
Always provide onPageChange - This callback is required for the component to function
Sync with URL - Update URL parameters when the page changes for shareable links
Scroll to top - Automatically scroll to the top when changing pages
Loading states - Show a loading indicator while fetching new page data
Validate page numbers - Ensure currentPage and totalPages are valid numbers
Consider page size - For large datasets, implement ellipsis to avoid rendering too many page links