8
Hooks

usePagination

A hook that handles pagination logic with page navigation and item display.

usePagination

The usePagination hook simplifies pagination logic in React applications. It handles page navigation, boundary detection, and generates accessible pagination controls for your content, whether you’re paginating a list, table, or any other content that needs to be split across multiple pages.

Items
Item 1
processing
Item 2
completed
Item 3
failed
Item 4
pending
Item 5
processing
Item 6
completed
Item 7
failed
Item 8
pending
Item 9
processing
Item 10
completed
Page 1 of 9
...

Installation

Install the usePagination hook using:

npx axionjs-ui add hook use-pagination

File Structure

use-pagination.ts

Parameters

PropTypeDefault
totalItems
number
Required
pageSize
number
Required
siblingCount
number
1
currentPage
number
1

Return Value

PropTypeDefault
currentPage
number
-
totalPages
number
-
pageSize
number
-
pageItems
(number | -1)[]
-
firstPage
() => void
-
previousPage
() => void
-
nextPage
() => void
-
lastPage
() => void
-
goToPage
(page: number) => void
-
canPreviousPage
boolean
-
canNextPage
boolean
-

Examples

Table Pagination

Create a paginated table with controls for changing page size:

Product Inventory
IDNamePriceStock
1Product 1$974
2Product 2$4345
3Product 3$2116
4Product 4$790
5Product 5$1048
Page 1 of 9
1

API Data Pagination

Use the hook with async data fetching:

function ApiPagination() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [totalItems, setTotalItems] = useState(0);
  const pageSize = 20;
  
  const {
    currentPage,
    totalPages,
    previousPage,
    nextPage,
    goToPage,
    canPreviousPage,
    canNextPage,
  } = usePagination({
    totalItems,
    pageSize,
  });
  
  // Fetch data when page changes
  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(
          `/api/items?page=${currentPage}&pageSize=${pageSize}`
        );
        const result = await response.json();
        
        setData(result.items);
        setTotalItems(result.total);
      } catch (error) {
        console.error("Error fetching data:", error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [currentPage, pageSize]);
  
  return (
    <div>
      {loading ? (
        <div>Loading...</div>
      ) : (
        <div className="items-list">
          {data.map(item => (
            <div key={item.id} className="item-card">
              {/* Item details */}
            </div>
          ))}
        </div>
      )}
      
      <div className="pagination">
        <button
          onClick={previousPage}
          disabled={!canPreviousPage || loading}
        >
          Previous
        </button>
        
        <span>
          Page {currentPage} of {totalPages}
        </span>
        
        <button
          onClick={nextPage}
          disabled={!canNextPage || loading}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Custom Page Size Controls

Allow users to control items per page:

function CustomPageSize() {
  const [pageSize, setPageSize] = useState(10);
  const totalItems = 100;
  
  const { currentPage, totalPages, pageItems, goToPage } = usePagination({
    totalItems,
    pageSize,
  });
  
  const handlePageSizeChange = (e) => {
    const newPageSize = Number(e.target.value);
    setPageSize(newPageSize);
    
    // Adjust current page to maintain approximate scroll position
    const currentIndex = (currentPage - 1) * pageSize;
    const newPage = Math.floor(currentIndex / newPageSize) + 1;
    goToPage(newPage);
  };
  
  return (
    <div>
      {/* Content */}
      
      <div className="pagination-controls">
        <div>
          <label>
            Items per page:
            <select value={pageSize} onChange={handlePageSizeChange}>
              <option value="5">5</option>
              <option value="10">10</option>
              <option value="25">25</option>
              <option value="50">50</option>
              <option value="100">100</option>
            </select>
          </label>
        </div>
        
        {/* Pagination controls */}
      </div>
    </div>
  );
}

Use Cases

  • Data Tables: Paginate rows of tabular data
  • Search Results: Display search results across multiple pages
  • Product Listings: Show products or items in pages
  • Comments & Reviews: Paginate user-generated content
  • Blog Posts: Navigate through blog post archives
  • Image Galleries: Display images across multiple pages
  • Admin Dashboards: Paginate lists of users, orders, or other resources
  • API Data: Handle pagination for data fetched from APIs

Algorithm

The usePagination hook uses a smart algorithm for generating the page numbers to display:

  1. If the total number of pages is small, it shows all page numbers
  2. If there are many pages, it shows:
    • First page
    • Ellipsis (…) if needed
    • A few pages around the current page (based on siblingCount)
    • Ellipsis (…) if needed
    • Last page

This approach keeps the pagination controls compact while maintaining good usability.

Accessibility

When implementing pagination with this hook, consider these accessibility best practices:

  • Add proper aria-label attributes to pagination buttons
  • Include a screen reader-accessible text describing the current page status
  • Ensure keyboard navigation works correctly
  • Maintain focus when changing pages
  • Consider adding aria-current="page" to the current page button

URL Integration

You can integrate the pagination state with the URL for shareable pages:

import { useRouter } from 'next/router';
 
function UrlPagination() {
  const router = useRouter();
  const { query } = router;
  
  // Get page from URL or default to 1
  const pageFromUrl = query.page ? Number(query.page) : 1;
  
  const { currentPage, totalPages, pageItems, goToPage } = usePagination({
    totalItems: 100,
    pageSize: 10,
    currentPage: pageFromUrl,
  });
  
  // Update URL when page changes
  useEffect(() => {
    if (pageFromUrl !== currentPage) {
      router.push({
        pathname: router.pathname,
        query: { ...query, page: currentPage },
      }, undefined, { shallow: true });
    }
  }, [currentPage, pageFromUrl, router, query]);
  
  return (
    <div>
      {/* Content */}
      
      {/* Pagination controls */}
      <div className="pagination">
        {pageItems.map((page, i) => (
          <React.Fragment key={i}>
            {page === -1 ? (
              <span>...</span>
            ) : (
              <button onClick={() => goToPage(page)}>
                {page}
              </button>
            )}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

Best Practices

  • Maintain a reasonable pageSize based on your content and UI design
  • Consider loading states during async data fetching
  • Provide visual indication of the current page
  • Include first/last page buttons for large datasets
  • Offer a way to adjust the number of items per page for user preference
  • Keep pagination controls in a consistent location
  • Use ellipsis (…) for large page ranges to maintain a clean UI
  • Consider “back to top” functionality when changing pages

On this page