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/pages/categoryView/CategoryProductsPagination.tsx
SearchProductsPaginationcatalogSearchthemes/<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

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/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: 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.