Offset Pagination
Offset Pagination
Offset pagination uses page numbers and items per page.
Basic Implementation
// hooks/usePagination.js
function usePagination({ totalItems, itemsPerPage = 10, initialPage = 1 }) {
const [currentPage, setCurrentPage] = useState(initialPage);
const totalPages = Math.ceil(totalItems / itemsPerPage);
const offset = (currentPage - 1) * itemsPerPage;
const goToPage = (page) => {
const pageNumber = Math.max(1, Math.min(page, totalPages));
setCurrentPage(pageNumber);
};
const goToNextPage = () => goToPage(currentPage + 1);
const goToPreviousPage = () => goToPage(currentPage - 1);
const goToFirstPage = () => goToPage(1);
const goToLastPage = () => goToPage(totalPages);
// Generate page numbers
const getPageNumbers = () => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
let end = Math.min(totalPages, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
};
return {
currentPage,
totalPages,
offset,
itemsPerPage,
goToPage,
goToNextPage,
goToPreviousPage,
goToFirstPage,
goToLastPage,
getPageNumbers,
hasNextPage: currentPage < totalPages,
hasPreviousPage: currentPage > 1,
};
}
Pagination Component
// components/Pagination.jsx
function Pagination({ currentPage, totalPages, onPageChange }) {
const pages = getPageNumbers(currentPage, totalPages);
return (
<nav className="pagination" aria-label="Pagination">
<button
onClick={() => onPageChange(1)}
disabled={currentPage === 1}
aria-label="First page"
>
«
</button>
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Previous page"
>
‹
</button>
{pages.map(page => (
<button
key={page}
onClick={() => onPageChange(page)}
className={page === currentPage ? 'active' : ''}
aria-current={page === currentPage ? 'page' : undefined}
>
{page}
</button>
))}
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
aria-label="Next page"
>
›
</button>
<button
onClick={() => onPageChange(totalPages)}
disabled={currentPage === totalPages}
aria-label="Last page"
>
»
</button>
</nav>
);
}
API Integration
function ProductList() {
const [page, setPage] = useState(1);
const [pageSize] = useState(20);
const { data, isLoading } = useQuery({
queryKey: ['products', page, pageSize],
queryFn: () => fetchProducts({ page, pageSize }),
keepPreviousData: true,
});
return (
<div>
{isLoading && <Spinner />}
<ProductTable products={data?.items || []} />
<Pagination
currentPage={page}
totalPages={data?.totalPages || 0}
onPageChange={setPage}
/>
</div>
);
}
Pros and Cons
Pros:
- Simple to implement
- Direct page access
- URL-friendly
Cons:
- Skips items on page changes
- Inconsistent with real-time data
- Performance issues with large offsets
Cursor Pagination
Cursor Pagination
Cursor pagination uses a pointer to the last item instead of page numbers.
How It Works
GET /api/products?limit=20&cursor=abc123
Response:
{
"items": [...],
"nextCursor": "xyz789",
"hasMore": true
}
Implementation
// hooks/useCursorPagination.js
function useCursorPagination(queryKey, fetchFn, { limit = 20 } = {}) {
const [cursors, setCursors] = useState(['']);
const [direction, setDirection] = useState('next');
const currentCursor = cursors[cursors.length - 1];
const { data, isLoading, isFetching } = useQuery({
queryKey: [...queryKey, currentCursor, limit],
queryFn: () => fetchFn({ cursor: currentCursor, limit }),
keepPreviousData: true,
});
const goToNextPage = () => {
if (data?.nextCursor) {
setCursors(prev => [...prev, data.nextCursor]);
setDirection('next');
}
};
const goToPreviousPage = () => {
if (cursors.length > 1) {
setCursors(prev => prev.slice(0, -1));
setDirection('prev');
}
};
return {
items: data?.items || [],
isLoading,
isFetching,
hasNextPage: data?.hasMore || false,
hasPreviousPage: cursors.length > 1,
goToNextPage,
goToPreviousPage,
totalItems: data?.total,
};
}
// Component
function ProductList() {
const {
items,
isLoading,
hasNextPage,
hasPreviousPage,
goToNextPage,
goToPreviousPage,
} = useCursorPagination(['products'], fetchProducts);
return (
<div>
{isLoading ? (
<Spinner />
) : (
<>
<ProductTable products={items} />
<div className="pagination">
<button onClick={goToPreviousPage} disabled={!hasPreviousPage}>
Previous
</button>
<button onClick={goToNextPage} disabled={!hasNextPage}>
Next
</button>
</div>
</>
)}
</div>
);
}
Pros and Cons
Pros:
- Consistent with real-time data
- Better performance for large datasets
- No skipped/duplicate items
Cons:
- No direct page access
- More complex implementation
- Cannot jump to specific page
Infinite Scroll vs Pagination
Infinite Scroll vs Pagination
When to Use Each
| Use Case | Recommended |
|---|---|
| E-commerce product listing | Pagination |
| Social media feed | Infinite scroll |
| Search results | Pagination |
| Chat messages | Infinite scroll |
| Admin dashboards | Pagination |
| Image galleries | Infinite scroll |
Pagination Component
function PaginatedList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
queryKey: ['items', page],
queryFn: () => fetchItems({ page, limit: 20 }),
});
return (
<div>
<List items={data?.items} />
<Pagination
current={page}
total={data?.total}
onChange={setPage}
/>
{/* SEO-friendly, accessible */}
{/* Users can jump to specific pages */}
{/* URL can reflect page state */}
</div>
);
}
Infinite Scroll Component
function InfiniteList() {
const {
items,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['items'],
queryFn: ({ pageParam }) => fetchItems({ cursor: pageParam }),
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
const allItems = items?.pages.flatMap(page => page.items) || [];
return (
<div>
<List items={allItems} />
{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
User Experience Considerations
Pagination Pros:
- Users can navigate to specific pages
- Clear sense of progress
- Better for data analysis
- URL shareable
Infinite Scroll Pros:
- More engaging browsing
- Better for content discovery
- Less clicking required
- Better mobile experience
Infinite Scroll Cons:
- Hard to return to specific position
- Footer/links hard to reach
- Performance issues with many items
- Accessibility challenges
Practice Problems
Create a reusable React component implementing Pagination. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Pagination using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Pagination for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is the main issue with offset pagination on large datasets?
2. What is the key advantage of cursor pagination?
3. When is infinite scroll most appropriate?
4. Why is pagination better for SEO?
5. What should you use to maintain scroll position with infinite scroll?
Flashcards
Question
What is offset pagination?
Click to reveal answer
Answer
Pagination that uses page numbers and items-per-page to calculate the starting position.
Question
What is cursor pagination?
Click to reveal answer
Answer
Pagination that uses a pointer to the last item for consistent, reliable page navigation.
Question
When should you use pagination over infinite scroll?
Click to reveal answer
Answer
For admin dashboards, search results, and scenarios where users need to navigate to specific positions.
Question
What is the main disadvantage of offset pagination?
Click to reveal answer
Answer
Items can be skipped or duplicated when data changes between page requests.
Question
What is Pagination?
Click to reveal answer
Answer
Pagination is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Offset pagination is simple but can skip items with real-time data
- 2.Cursor pagination provides consistent results for large datasets
- 3.Choose pagination for navigation-heavy UIs, infinite scroll for discovery
- 4.Always consider accessibility and SEO when choosing pagination strategy
- 5.Maintain URL state for shareable pagination positions
Interview Tips
- •Explain the difference between offset and cursor pagination
- •Discuss when to use pagination vs infinite scroll
- •Know the trade-offs of each approach
Cheat Sheet
Pagination Cheat Sheet
Offset Pagination
const offset = (page - 1) * limit;
fetch(`/api/items?offset=${offset}&limit=${limit}`);
Cursor Pagination
fetch(`/api/items?cursor=${lastId}&limit=${limit}`);
When to Use
- Pagination: Admin dashboards, search, data tables
- Infinite Scroll: Social feeds, image galleries, content discovery
Key Considerations
- Offset can skip items with real-time data
- Cursor provides consistent results
- Infinite scroll needs position management