Skip to main content

Overview

Tienda ETCA implements pagination for the product gallery using React-Bootstrap’s Pagination component. The system displays 5 products per page and includes smooth scrolling to the top of the gallery when changing pages.

Implementation

Pagination is implemented in the ProductList component at ~/workspace/source/src/components/ProductList.jsx:5-70.

Import

import Pagination from 'react-bootstrap/Pagination'

State Management

const [currentPage, setCurrentPage] = useState(1)
const itemsPerPage = 5
The pagination state tracks the current page number, starting from 1.

Pagination Logic

The component calculates which products to display based on the current page:
const ProductList = () => {
  const { productosFiltrados, busqueda, setBusqueda } = useContext(CartContext)

  const [currentPage, setCurrentPage] = useState(1)
  const itemsPerPage = 5
  const indexOfLast = currentPage * itemsPerPage
  const indexOfFirst = indexOfLast - itemsPerPage
  const currentProducts = productosFiltrados.slice(indexOfFirst, indexOfLast)
  const totalPages = Math.ceil(productosFiltrados.length / itemsPerPage)
  
  // ...
}

Calculation Breakdown

1

Calculate Last Index

const indexOfLast = currentPage * itemsPerPage
For page 1: 1 * 5 = 5
2

Calculate First Index

const indexOfFirst = indexOfLast - itemsPerPage
For page 1: 5 - 5 = 0
3

Slice Products Array

const currentProducts = productosFiltrados.slice(indexOfFirst, indexOfLast)
For page 1: productosFiltrados.slice(0, 5) returns items 0-4
4

Calculate Total Pages

const totalPages = Math.ceil(productosFiltrados.length / itemsPerPage)
For 23 products: Math.ceil(23 / 5) = 5 pages

Pagination UI

The pagination component renders at ~/workspace/source/src/components/ProductList.jsx:50-70:
<div className="paginacion-container">
  <Pagination className="pagination">
    <Pagination.Prev
      onClick={() => setCurrentPage(p => Math.max(p - 1, 1))}
      disabled={currentPage === 1}
    />
    {Array.from({ length: totalPages }, (_, i) => (
      <Pagination.Item
        key={i + 1}
        active={i + 1 === currentPage}
        onClick={() => setCurrentPage(i + 1)}
      >
        {i + 1}
      </Pagination.Item>
    ))}
    <Pagination.Next
      onClick={() => setCurrentPage(p => Math.min(p + 1, totalPages))}
      disabled={currentPage === totalPages}
    />
  </Pagination>
</div>

Component Features

Previous Button

Decrements page by 1
Math.max(p - 1, 1)
Disabled on page 1

Page Numbers

Dynamically generated based on total pages
Array.from({ length: totalPages })
Active page highlighted

Next Button

Increments page by 1
Math.min(p + 1, totalPages)
Disabled on last page

Smooth Scrolling

When users navigate between pages, the gallery automatically scrolls to the top for better UX:
const galeriaRef = useRef(null);
const isFirstRender = useRef(true);

useEffect(() => {
  if (isFirstRender.current) {
    isFirstRender.current = false;
    return;
  }

  if (galeriaRef.current) {
    galeriaRef.current.scrollIntoView({ behavior: 'smooth' });
  }
}, [currentPage]);
The isFirstRender check prevents scrolling on initial page load, only scrolling when users actively change pages.

Scroll Target

The gallery container is marked with a ref:
<div className='galeria' ref={galeriaRef}>
  {currentProducts.map(product => (
    <Product key={product.id} product={product} />
  ))}
</div>
Pagination automatically adapts to search results. When users search for products, the pagination recalculates based on filtered results:
const { productosFiltrados } = useContext(CartContext)

const currentProducts = productosFiltrados.slice(indexOfFirst, indexOfLast)
const totalPages = Math.ceil(productosFiltrados.length / itemsPerPage)
totalPages = Math.ceil(23 / 5) = 5 pages
Shows: Page 1, 2, 3, 4, 5

Full Component Code

Complete implementation from ~/workspace/source/src/components/ProductList.jsx:7-72:
const ProductList = () => {
  const { productosFiltrados, busqueda, setBusqueda } = useContext(CartContext)

  const [currentPage, setCurrentPage] = useState(1)
  const itemsPerPage = 5
  const indexOfLast = currentPage * itemsPerPage
  const indexOfFirst = indexOfLast - itemsPerPage
  const currentProducts = productosFiltrados.slice(indexOfFirst, indexOfLast)
  const totalPages = Math.ceil(productosFiltrados.length / itemsPerPage)

  const galeriaRef = useRef(null);
  const isFirstRender = useRef(true);

  useEffect(() => {
    if (isFirstRender.current) {
      isFirstRender.current = false;
      return;
    }

    if (galeriaRef.current) {
      galeriaRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  }, [currentPage]);

  return (
    <>
      <div className="busqueda-container">
        <input
          type='text'
          placeholder='Buscar productos...'
          value={busqueda}
          onChange={(e) => setBusqueda(e.target.value)}
        />
      </div>

      <div className='galeria' ref={galeriaRef}>
        {currentProducts.map(product => (
          <Product key={product.id} product={product} />
        ))}
      </div>

      <div className="paginacion-container">
        <Pagination className="pagination">
          <Pagination.Prev
            onClick={() => setCurrentPage(p => Math.max(p - 1, 1))}
            disabled={currentPage === 1}
          />
          {Array.from({ length: totalPages }, (_, i) => (
            <Pagination.Item
              key={i + 1}
              active={i + 1 === currentPage}
              onClick={() => setCurrentPage(i + 1)}
            >
              {i + 1}
            </Pagination.Item>
          ))}
          <Pagination.Next
            onClick={() => setCurrentPage(p => Math.min(p + 1, totalPages))}
            disabled={currentPage === totalPages}
          />
        </Pagination>
      </div>
    </>
  )
}

Customization Options

Change Items Per Page

const itemsPerPage = 10; // Show 10 products per page
useEffect(() => {
  setCurrentPage(1);
}, [busqueda]);

Custom Page Button Styling

<Pagination.Item
  key={i + 1}
  active={i + 1 === currentPage}
  onClick={() => setCurrentPage(i + 1)}
  className="custom-page-item"
>
  {i + 1}
</Pagination.Item>

Best Practices

Use Math.max() and Math.min() to ensure page numbers stay within valid range:
Math.max(p - 1, 1) // Never go below page 1
Math.min(p + 1, totalPages) // Never exceed last page
Disable Previous button on first page and Next button on last page for better UX:
disabled={currentPage === 1}
disabled={currentPage === totalPages}
Skip scrolling on initial render to prevent jarring page load:
if (isFirstRender.current) {
  isFirstRender.current = false;
  return;
}
Always calculate total pages based on filtered results for search compatibility:
Math.ceil(productosFiltrados.length / itemsPerPage)
For large product catalogs with many pages, consider implementing page number ellipsis (e.g., “1 … 5 6 7 … 20”) to keep the pagination UI clean.

Build docs developers (and LLMs) love