Skip to main content

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 children function 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 ComponentRouteOverride Path in Theme
CategoryProductsPaginationcategoryViewthemes/<name>/src/components/frontStore/catalog/CategoryProductsPagination.tsx
SearchProductsPaginationcatalogSearchthemes/<name>/src/components/frontStore/catalog/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​

NameTypeRequiredDefaultDescription
totalnumberYes-Total number of items
limitnumberYes-Items per page
currentPagenumberYes-Current page number
onPageChange(page: number) => voidNo-Callback when page changes
scrollToTopbooleanNotrueScroll to top on page change
scrollBehavior'auto' | 'smooth'No'smooth'Scroll animation behavior
children(props: PaginationRenderProps) => ReactNodeYes-Render function receiving pagination props

Render Props​

NameTypeDescription
currentPagenumberCurrent page number
totalPagesnumberTotal number of pages
totalnumberTotal number of items
limitnumberItems per page
hasNextbooleanTrue if there is a next page
hasPrevbooleanTrue if there is a previous page
startItemnumberFirst item index on current page
endItemnumberLast 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) => booleanCheck if page is active
isValidPage(page: number) => booleanCheck if page number is valid
isLoadingbooleanTrue during page navigation
getDisplayText() => stringReturns "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/components/frontStore/catalog/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;

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: usePaginationLogic for direct usage
  • Display Helpers: getDisplayText, getPageInfo for common UI patterns
  • Loading State: Tracks navigation loading state
  • Type Safe: Full TypeScript support


Support us


EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.