Search code examples
reactjspaginationresponsive

Updating results per page only working on first page of pagination-react


I am able to make my pagination work as it should, however when I want the user to change the amount of items they see each page, it only paginates the first page and shows the remaining of the results on the second page.

I also keep getting an error that my pageCount prop is not an integer and I need to use Math.ciel().However I'm confused as where I need to use it.

import { useContext, useState } from 'react';
import Searchbar from '../Components/Searchbar';
import OptionButtons from '../Components/OptionButtons';
import { AppContext } from '../Context/Context';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBasketShopping } from '@fortawesome/free-solid-svg-icons';
import { Link } from 'react-router-dom';
import useAxios from '../Hooks/useAxios';
import savedHook from '../Hooks/savedHook';
import PriceSlider from '../Components/PriceSlider';
import BrandList from '../Components/BrandList';
import Pagination from 'react-paginate'
import Drawer from 'react-modern-drawer'
import Accordion from '../Components/Accordion';
import 'react-modern-drawer/dist/index.css'
import '../Assets/Styles/Shop.css'




function Shop() {

  // States //////////////////////////////////////////////////////////////
  const { products, setProducts, isLoading, serverErr, getProductsByBrand,
    getProductsByType, selectAProduct, error, filterProduct, priceRangeProducts, openFilterDrawer, filterDrawer } = useAxios('https://makeup-api.herokuapp.com/api/v1/products.json')
  // saved icon to shaded
  const { likedIndex, changeIcon } = savedHook()

  const accordionData = [
    {
      'id': 1,
      'heading': 'Price Range',
      'content': <PriceSlider onSlider={priceRangeProducts} />
    },
    {
      'id': 2,
      'heading': 'Brand',
      'content': <BrandList brandDropDown={getProductsByBrand} />
    }
  ]

  // useContext for the add to cart 
  const Cartstate = useContext(AppContext)
  const dispatch = Cartstate.dispatch;

  // Usecontext for the saved array as it uses a different function
  const Savestate = useContext(AppContext)
  const saveDispatch = Savestate.saveDispatch


  // pagination 
  // set to 0 because if I set it to 1 then it doesn't show all the data
  const [currentPage, setCurrentPage] = useState(0)
  const [productPerPage, setProductPerPage] = useState(30)
  // const [itemOffset, setItemOffset] = useState(0);
  const pageCount = Math.ceil(products.length / productPerPage);

  const handleChanges = (e) => {
    setProductPerPage(e.target.value)
    console.log(pageCount)
    console.log(productPerPage)
  }

  const pagesVisited = currentPage * productPerPage

  // How I want the data to be shown on the page
  const displayProducts = products.slice(
    pagesVisited, pagesVisited + productPerPage
    )
    .map((item, index) => {
      return (
        <div className='singleCard h-full flex flex-col mx-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-100 hover: duration-300 hover:shadow-lg' key={item.id}>
          < button className='mx-1 my-1' value={item.brand + item.product_type} onClick={() => { changeIcon(index); saveDispatch({ type: 'SAVE', saveIt: item }) }}>
            {likedIndex[index] ?
              (
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="redHeart w-6 h-6">
                  <path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
                </svg>
              )
              : (
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" >
                  <path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
                </svg>
              )
            }

          </button>
          <Link to={`/product/${item.id}`} name={item.brand}>
            <img className='productImg ml-8 mb-2 md:ml-8 xl:ml-14' src={item.api_featured_image} alt={item.brand + item.product_type}></img>
            {/* To display the brand name with as sentence case */}
            <div className='productText px-2 flex flex-col content-end md:px-1 '>
              <p className='productType'>{item?.product_type ? item.product_type.charAt(0) + item.product_type.slice(1).toLowerCase().split('_').join(' ') : item.product_type}</p>
              <p className='productBrand'>
                {item?.brand ? item.brand.charAt(0).toUpperCase() + item.brand.slice(1).toLowerCase() : item.brand} </p>
              <p className='productName'>{item?.name ? item.name.charAt(0).toUpperCase() + item.name.slice(1).toLowerCase() : item.name}</p>
            </div>
          </Link>
          <div className='flex flex-row mt-auto mx-2 my-2'>
            <div className='mr-auto mt-auto'>
              <p className='productPriceShop'><span className='circleShadow '>£{Number(item.price).toFixed(2)}</span>
              </p>
            </div>
            <div className='addToCart ml-auto mt-auto '>
              <button className='basketProduct' onClick={() => dispatch({ type: 'ADD', payload: item })}>
                <FontAwesomeIcon icon={faBasketShopping} />
              </button>
            </div>
          </div>
        </div>
      )
    })

  // calucates total pages need for all the data with how many items to be shown each page
  // Math.ceil rounds the number of pages to a whole integer
  //callback function invoked with the updated page value when the page is changed.
  const changePage = ({ selected }) => {
    setCurrentPage(selected)
    console.log(pagesVisited)
    console.log(selected)
  }

  const sortThis = (e) => {
    const sorting = e.target.value
    const productList = [...products]
    const prices = productList.sort((a, b) => {
      return sorting === 'asc' ? a.price - b.price : b.price - a.price
    });

    setProducts(prices)
  }

  return (
    <>
      <div className='shopTopBanner'>
        <h1>Cosmetics for you</h1>
        <ol className='routeProductPage flex flex-row mr-2'>
          <li><Link to='/'>Home/</Link></li>
          <li><Link to='/shop'>Shop</Link></li>
        </ol>
      </div>
      <div className='shopColumn'>
        <div className='leftShopColumn'>
          <div className='searchBar md:hidden'>
            <Searchbar
              onSearch={getProductsByBrand}
              onFilter={getProductsByType}
              onInput={filterProduct}
            />
          </div>

          <div className='priceSlider hidden md:inline-block'>
            <PriceSlider
              onSlider={priceRangeProducts}></PriceSlider>
          </div>

          <div className='productTypeButtons'>
            <OptionButtons onButton={selectAProduct} />
          </div>

          <div className='mobileSortMenu flex flex-row md:hidden'>
            <div>
              <button onClick={openFilterDrawer}>
              More Options
            </button>
            </div>
            <div className='productPerPageMobile'>
              <select
                value={productPerPage}
                onChange={handleChanges}>
                <option defaultValue>Products per page</option>
                <option value={5}>
                  5 products
                </option>
                <option value={10}>
                  10 products
                </option>
                <option value={20}>
                  20 products
                </option>
              </select>
            </div>
            <div className='sortingDropdownMobile'>
                <select onChange={sortThis}>Sort it out
                  <option defaultValue>Sort</option>
                  <option value={'asc'}>Ascending</option>
                  <option value={'desc'}>Descending</option>
                </select>
            </div>
          </div>

          {filterDrawer ?
            <>
              <Drawer
                open={filterDrawer}
                onClose={openFilterDrawer}
                direction='bottom'>
                <div className='flex flex-row'>
                  <h1>Filter and Sort </h1>
                  <h1 onClick={openFilterDrawer}>Close</h1>

                </div>
                <div className='flex flex-col'>
                  <ul className="accordion">
                    {accordionData.map(({ heading, content }) => (
                      <Accordion heading={heading} content={content} />
                    ))}
                  </ul>
                </div>
              </Drawer>
            </>
            : null}

          <div className='listOfBrands hidden md:inline-block'>
            <BrandList brandDropDown={getProductsByBrand}></BrandList>
          </div>

          <div className='sortingDropdown hidden md:inline-block'>
            <select onChange={sortThis}>Sort it out
              <option defaultValue>Sort</option>
              <option value={'asc'}>Ascending</option>
              <option value={'desc'}>Descending</option>
            </select>
          </div>

          <div className='productsPerPage hidden md:inline-block'>
            <select
              value={productPerPage}
              onChange={handleChanges}>
              <option defaultValue>Products per page</option>
              <option value={5}>
                  5 products
                </option>
                <option value={10}>
                  10 products
                </option>
                <option value={20}>
                  20 products
                </option>
            </select>
          </div>

          <button>
            Reset filters
          </button>

        </div>

        <div className='rightShopColumn'>
          <div className='searchBar hidden md:inline-block'>
            <Searchbar
              onSearch={getProductsByBrand}
              onFilter={getProductsByType}
              onInput={filterProduct}
            />
          </div>


          {serverErr && <div>{serverErr}</div>}
          {error ? <div>{error}</div>:
                    <div>
                    {!isLoading ? <>
                      {products.length ?
                        <>
                          <div className='shopCards grid grid-cols-2 flex-wrap py-4 mr-1 md:grid-cols-4 lg:grid-cols-4'>
                            {displayProducts}
                          </div>
                          <div className='flex flex-col'>
                            <div >
                              <h1> Showing {pagesVisited===0?1:pagesVisited} - {pagesVisited===0?30: pagesVisited*2} of {products.length} products</h1>
                            </div>
                            <div>
                              <Pagination
                                previousLabel={'Previous page'}
                                // onChange={handleChanges}
                                nextLabel={'Next page'}
                                pageCount={pageCount}
                                pageClassName='pageNoneDisplay'
                                breakClassName='pageNoneDisplay'
                                onPageChange={changePage}
                                containerClassName={'paginationBtns py-3'}
                                previousLinkClassName={'previousBtn'}
                                nextLinkClassName={'nextBtn'}
                                disabledClassName={'paginationDisbaled'}
                                activeClassName={'paginationActive'}
                                pageRangeDisplayed={1}
                                marginPagesDisplayed={2}
                              />
                            </div>
                          </div>
                        </>
                        : <h1>No results found</h1>}
                    </> :
                      <>
                        <h1>Loading...</h1>
                      </>
                    }
                  </div >
        }        </div>
      </div>
    </>
  )
}


export default Shop; 

Would love some advice as I've been stuck on this for a couple of days now!

I have attached a simplified codesandbox: https://github.com/chinapicke/shopit/blob/bb826719ea3a99bf1864ff676821e7dcc2d5ec5d/src/Pages/Shop.js


Solution

  • Without having run your code I'd say your underlying problem is the productPerPage value - as you already pointed out, it complains about pageCount not being an integer, but actually the issue is using setProductPerPage with the raw event.target.value, which is a string - you would need to use parseInt or something to get a number, and set it to the state correctly.

    The current result is, you are using slice() to get a portion of your products, and it seems like, once you get to the 2nd page, the 2nd argument you are providing to slice is not a valid number, therefore you end up slicing like this: slice(pagesVisited), meaning only providing one argument, and therefore receiving all elements after the start value, as shown here for example.

    Just a side note: be careful about proper naming, it was difficult for me to understand your code, for instance, pagesVisited should rather be called offset etc. - there are some good practises out there that will improve maintainability of your code immensely :)