Pagination
Description
A headless render props component that provides pagination state and navigation functions. It manages page calculation, URL synchronization, page data fetching via AppContext, and scroll behavior — but renders no UI of its own. Also exports a usePaginationLogic hook and pre-built renderers.
Role in Theming
Pagination is one of EverShop's headless components — it owns the pagination logic while leaving all UI decisions to its parent:
- Pagination renders nothing. It returns only what its
childrenfunction renders. - Theme developers do not override Pagination. Instead, they override the parent components that consume it (
CategoryProductsPagination,SearchProductsPagination). - The logic stays stable across themes. URL parameter updates, GraphQL page data fetching, history state management, scroll-to-top behavior, and page validation are all encapsulated in Pagination.
Theme Override Points
Pagination is consumed by these components, which are the actual override targets for theme developers:
| Parent Component | Route | Override Path in Theme |
|---|---|---|
| CategoryProductsPagination | categoryView | themes/<name>/src/pages/categoryView/CategoryProductsPagination.tsx |
| SearchProductsPagination | catalogSearch | themes/<name>/src/pages/catalogSearch/SearchProductsPagination.tsx |
Import
import {
Pagination,
usePaginationLogic,
DefaultPaginationRenderer,
CompactPaginationRenderer,
InputPaginationRenderer
} from '@components/frontStore/Pagination';
Usage
import { Pagination } from '@components/frontStore/Pagination';
function ProductPagination({ total, limit, currentPage }) {
return (
<Pagination total={total} limit={limit} currentPage={currentPage}>
{({ currentPage, totalPages, hasNext, hasPrev, goToNext, goToPrev, goToPage, getPageNumbers, isCurrentPage, isLoading }) => (
<nav>
<button onClick={goToPrev} disabled={!hasPrev || isLoading}>
Previous
</button>
{getPageNumbers().map(page => (
<button
key={page}
onClick={() => goToPage(page)}
disabled={isCurrentPage(page) || isLoading}
className={isCurrentPage(page) ? 'active' : ''}
>
{page}
</button>
))}
<button onClick={goToNext} disabled={!hasNext || isLoading}>
Next
</button>
</nav>
)}
</Pagination>
);
}
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| total | number | Yes | - | Total number of items |
| limit | number | Yes | - | Items per page |
| currentPage | number | Yes | - | Current page number |
| onPageChange | (page: number) => void | No | - | Callback when page changes |
| scrollToTop | boolean | No | true | Scroll to top on page change |
| scrollBehavior | 'auto' | 'smooth' | No | 'smooth' | Scroll animation behavior |
| children | (props: PaginationRenderProps) => ReactNode | Yes | - | Render function receiving pagination props |
Render Props
| Name | Type | Description |
|---|---|---|
| currentPage | number | Current page number |
| totalPages | number | Total number of pages |
| total | number | Total number of items |
| limit | number | Items per page |
| hasNext | boolean | True if there is a next page |
| hasPrev | boolean | True if there is a previous page |
| startItem | number | First item index on current page |
| endItem | number | Last item index on current page |
| goToPage | (page: number) => Promise<void> | Navigate to specific page |
| goToNext | () => Promise<void> | Navigate to next page |
| goToPrev | () => Promise<void> | Navigate to previous page |
| goToFirst | () => Promise<void> | Navigate to first page |
| goToLast | () => Promise<void> | Navigate to last page |
| getPageNumbers | (range?: number) => number[] | Get visible page numbers (default range: 5) |
| isCurrentPage | (page: number) => boolean | Check if page is active |
| isValidPage | (page: number) => boolean | Check if page number is valid |
| isLoading | boolean | True during page navigation |
| getDisplayText | () => string | Returns "Showing X-Y of Z results" |
| getPageInfo | () => { showing: string; total: string } | Returns showing range and total as strings |
Pre-Built Renderers
Pagination exports three ready-to-use renderers that can be passed the render props directly:
DefaultPaginationRenderer
Full page number navigation with ellipsis for large page counts.
import { Pagination, DefaultPaginationRenderer } from '@components/frontStore/Pagination';
function ProductPagination({ total, limit, currentPage }) {
return (
<Pagination total={total} limit={limit} currentPage={currentPage}>
{(renderProps) => (
<DefaultPaginationRenderer renderProps={renderProps} showInfo />
)}
</Pagination>
);
}
CompactPaginationRenderer
Previous/Next buttons with page info text.
import { Pagination, CompactPaginationRenderer } from '@components/frontStore/Pagination';
function ProductPagination({ total, limit, currentPage }) {
return (
<Pagination total={total} limit={limit} currentPage={currentPage}>
{(renderProps) => (
<CompactPaginationRenderer renderProps={renderProps} />
)}
</Pagination>
);
}
InputPaginationRenderer
Page number input with First/Last buttons.
import { Pagination, InputPaginationRenderer } from '@components/frontStore/Pagination';
function ProductPagination({ total, limit, currentPage }) {
return (
<Pagination total={total} limit={limit} currentPage={currentPage}>
{(renderProps) => (
<InputPaginationRenderer renderProps={renderProps} />
)}
</Pagination>
);
}
usePaginationLogic Hook
For direct usage without the render props wrapper:
import { usePaginationLogic } from '@components/frontStore/Pagination';
function CustomPagination({ total, limit, currentPage }) {
const {
currentPage: page,
totalPages,
hasNext,
hasPrev,
goToPage,
goToNext,
goToPrev,
isLoading
} = usePaginationLogic(total, limit, currentPage);
return (
<div>
<button onClick={goToPrev} disabled={!hasPrev}>Prev</button>
<span>{page} / {totalPages}</span>
<button onClick={goToNext} disabled={!hasNext}>Next</button>
</div>
);
}
Examples
Theme Override Example
A theme developer overrides CategoryProductsPagination to use a compact pagination style. Create this file in your theme:
themes/my-theme/src/pages/categoryView/CategoryProductsPagination.tsx
import { Pagination } from '@components/frontStore/Pagination';
function CategoryProductsPagination({ total, limit, currentPage }) {
if (total <= limit) return null;
return (
<Pagination total={total} limit={limit} currentPage={currentPage}>
{({ currentPage, totalPages, hasNext, hasPrev, goToNext, goToPrev, getDisplayText, isLoading }) => (
<div className="my-theme-pagination">
<p>{getDisplayText()}</p>
<div className="controls">
<button
onClick={goToPrev}
disabled={!hasPrev || isLoading}
className="my-theme-prev-btn"
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
onClick={goToNext}
disabled={!hasNext || isLoading}
className="my-theme-next-btn"
>
Next
</button>
</div>
</div>
)}
</Pagination>
);
}
export default CategoryProductsPagination;
export const layout = {
areaId: 'categoryPageMain',
sortOrder: 50
};
Behavior
URL Synchronization
Page changes update the URL page query parameter and push to browser history. This enables back/forward navigation and shareable URLs.
Page Data Fetching
Navigation triggers AppContextDispatch.fetchPageData() with the new URL, which refetches the GraphQL page data for the new page.
Scroll to Top
By default, page changes scroll to the top of the page with smooth animation. Disable with scrollToTop={false} or change to instant with scrollBehavior="auto".
Page Validation
goToPage clamps the page number between 1 and totalPages. Navigating to the current page is a no-op. Navigation is blocked while a fetch is in progress.
Features
- Headless: Renders no UI — full control via render props
- URL Sync: Page parameter synced with URL
- Page Data Fetch: Automatically fetches new page data
- Scroll Management: Configurable scroll-to-top on navigation
- Pre-Built Renderers: Three ready-to-use renderer components
- Hook API:
usePaginationLogicfor direct usage - Display Helpers: getDisplayText, getPageInfo for common UI patterns
- Loading State: Tracks navigation loading state
- Type Safe: Full TypeScript support
Related Components
- ProductFilter - Product filtering
- ProductList - Product listing
Support us
EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.