I have a pagination component I am exporting to paginate set of products in another page but it doesn't seem to work well as there are 4 items in the database but the only number visible in the pagination list is just 1 and I can't figure out what's wrong
components/Pagination.jsx
import React from 'react'
const Pagination = ({postsPerPage, totalPosts, paginate}) => {
const pageNumbers = []
for(let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++){
pageNumbers.push(i)
}
return (
<div>
<div className="pagination page navigation">
<ul className="Pagination-list">
{pageNumbers.map(number =>
<li key={number} className="page-item">
<a onClick={() => paginate(number)} href="#" className="page-link">
{number}
</a>
</li>
)}
</ul>
</div>
</div>
)
}
export default Pagination
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { motion } from 'framer-motion';
import { Product, FooterBanner, HeroBanner, Pagination } from '../components'
const Home = ({products, bannerData}) => {
// pagination
const [currentPage, setCurrentPage] = useState(1)
const [postsPerPage] = useState(1)
const indexOfLastPost = currentPage * postsPerPage
const indexofFirstPost = indexOfLastPost - postsPerPage
const currentPosts = products.slice(indexofFirstPost, indexOfLastPost)
const paginate = (pageNumber) => setCurrentPage(pageNumber)
// search filter
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
const [animateCard, setAnimateCard] = useState({ y: 0, opacity: 1 });
useEffect(() => {
setProductItems(currentPosts)
setFilterWork(currentPosts)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setAnimateCard([{ y: 100, opacity: 0 }]);
setTimeout(() => {
setAnimateCard([{ y: 0, opacity: 1 }]);
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.slice(0).reverse().map((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<motion.div
animate={animateCard}
transition={{ duration: 0.5, delayChildren: 0.5 }}
>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<Pagination postsPerPage={postsPerPage} totalPosts={filterWork.length} paginate={paginate} />
</motion.div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below describes my explanation
I realized I wasn't passing in the products that was passed as props into the imported Pagination component to give access to all other Pagination features to work.
So I replaced the filterWork hook
<Pagination postsPerPage={postsPerPage} totalPosts={filterWork.length} paginate={paginate} />
With
<Pagination postsPerPage={postsPerPage} totalPosts={products.length} paginate={paginate} />
I also added currentPosts to data that should be filtered alongside filterWork
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
with
<div className='products-container'>
{
filterWork && currentPosts.map((product) => <Product key={product._id} product={product} />)
}
</div>
Related
I'm trying to build a clone of a MovieDataBase site in react, and when I update my state variable the page jumps ( it jumps up exactly 60px, for whatever reason, and then back down again each time I toggle the switch). I thought it was maybe because I'm interacting with the DOM to get the toggle switch to work, but that doesn't seem to be the issue. I've also been told that happens with styled components, which I don't think I have (still pretty new to REACT, so maybe???). Anyway - I can't figure out why this is happening. I've included the code for the component in question below.
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth + 1}px`
);
el.classList.add("selected");
};
const isMovieSelected = genre === "movie";
const isTvSelected = genre === "tv";
const movieData = "movie";
const tvData = "tv";
return (
<section className="container movie-list">
<div className="flex flex--align-center">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle
onChange={changeHandler}
selected={isMovieSelected}
data={movieData}
>
In Theaters
<div className="background"></div>
</Toggle>
<Toggle
onChange={changeHandler}
selected={isTvSelected}
data={tvData}
>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected, data } = props;
const className = selected ? "switch--option selected" : "switch--option";
return (
<div className={className}>
<h3>
<a data-genre={data} onClick={onChange} className="switch--anchor">
{children}
</a>
</h3>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path, id } = props;
return (
<div key={id} className="card">
<div className="image">
<img src={imageUri + "w500" + poster_path} />
</div>
<p>{title}</p>
</div>
);
};
Two components are used to process the image operation. First component is ShowFormsList inside it onDownloadFile is used and another component ShowImgList which is used to pass onDownloadFile method and ImgList as props .
ShowFormsList.jsx
import React, { useEffect, useContext } from "react";
import PropTypes from "prop-types";
import {
Subheading,
Accordion,
AccordionItem,
} from "#contentful/forma-36-react-components";
import Jarvis from "../../utils/Jarvis";
import LoadingContext from "../../context/LoadingContext";
import ShowImgList from "./ShowImgList";
const ShowFormsList = ({ sdk }) => {
const [view, setView] = React.useState(null);
const jarvisSdk = new Jarvis(sdk);
const { showLoading, hideLoading } = useContext(LoadingContext);
const fieldType = (sdk && sdk.field.id) || "";
// const [update, setUpdate] = React.useState(false);
const autoScroll = () => {
sdk.window.startAutoResizer();
};
useEffect(() => {
sdk.window.startAutoResizer();
});
const getFormData = async () => {
showLoading();
const formData = await jarvisSdk.getFormData(sdk.ids.entry);
const formArr =
formData &&
formData.length > 0 &&
formData.map((form) => {
const formObj = {
formName: form.formName,
formId: form.id,
formDescription: form.description,
};
// const imgArr = [];
const imgArr =
form.formImages &&
form.formImages.length > 0 &&
form.formImages.map((img) => {
console.log(img);
const imgObj = {
imageName: img.imageName,
type: img.type,
extension: img.imageName.split(".")[1],
id: img.id,
};
// imgArr.push(imgObj);
return imgObj;
});
formObj.images = imgArr;
return formObj;
// formArr.push(formObj);
});
if (formArr && formArr.length > 0) {
setView(formArr);
}
hideLoading();
};
useEffect(async () => {
await getFormData();
}, []);
const onDownloadFile = async (id) => {
showLoading();
const downloadedFile = await jarvisSdk.downloadFormFile(id, fieldType);
const contentDispositionHeader = downloadedFile.responseObj.request
.getResponseHeader("Content-disposition")
.split("filename=");
const fileName =
contentDispositionHeader[contentDispositionHeader.length - 1];
const url = window.URL.createObjectURL(new Blob([downloadedFile.blob]));
const link = document.createElement("a");
link.setAttribute("download", `${fileName}`);
link.setAttribute("id", Math.random() + fileName);
link.href = url;
document.body.appendChild(link);
link.click();
link.remove();
hideLoading();
};
return (
<>
{view && view.length > 0 && (
<div className="App">
<div className="Card">
<div className="container">
<Accordion>
{view.map((item) => (
<AccordionItem
key={item.formId}
title={
<a
className="text-decoration"
href={`https://app.contentful.com/spaces/${sdk.ids.space}/environments/${sdk.ids.environment}/entries/${item.formId}`}
target="_black"
>
{item.formName}
</a>
}
onExpand={() => autoScroll()}
onCollapse={() => autoScroll()}
>
<ShowImgList
key={item.formId}
images={item.images}
onDownloadFile={onDownloadFile}
sdk={sdk}
/>
</AccordionItem>
))}
</Accordion>
</div>
</div>
</div>
)}
{!view && (
<div className="App">
<div className="Card">
<div className="container">
<div className="dropZoneContainer">
<div className="Dropzone">
<Subheading className="d-color">No data found.</Subheading>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
};
const propTypes = {
sdk: PropTypes.instanceOf(Object),
};
ShowFormsList.defaultProps = {
sdk: undefined,
};
ShowFormsList.propTypes = propTypes;
export default ShowFormsList;
The second component is used to call the onDownloadFile method and perform the UI part operation.
ShowImgList.jsx
import {
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TabPanel,
Icon,
} from "#contentful/forma-36-react-components";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
const ShowImgList = (props) => {
const { images, onDownloadFile, sdk } = props;
useEffect(() => {
sdk.window.startAutoResizer();
});
const defaultTab = "first";
return (
<div className="fileVersionContainer">
<TabPanel id={defaultTab} className="versionContainer">
<div>
<Table>
<TableHead>
<TableRow>
<TableCell>Image</TableCell>
<TableCell>File Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>Download</TableCell>
</TableRow>
</TableHead>
<TableBody>
{images &&
images.length > 0 &&
images.map((aImg) => (
<TableRow key={aImg.id}>
<TableCell key={`${aImg.id}cell`}>
<Icon key={`${aImg.id}asset`} icon="Asset" />
</TableCell>
<TableCell key={`-name-${aImg.id}`}>
{aImg.imageName}
</TableCell>
<TableCell key={`-type-${aImg.id}`}>
{aImg.extension}
</TableCell>
<TableCell key={`-download-${aImg.id}`}>
<Icon
icon="Download"
className="cursor-pointer"
onClick={() => onDownloadFile(aImg.id)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</TabPanel>
</div>
);
};
const propTypes = {
sdk: PropTypes.instanceOf(Object),
images: PropTypes.instanceOf(Array),
onDownloadFile: PropTypes.func,
};
ShowImgList.defaultProps = {
sdk: undefined,
images: undefined,
onDownloadFile: undefined,
};
ShowImgList.propTypes = propTypes;
export default ShowImgList;
I'm working on an ecommerce site. Currently, I have a page which maps over all of the items from the API and displays them on screen.
I'm trying to make it so that when one of the mapped items is clicked, the user will be taken to a new page ("Item") featuring just that item. I'm using React Router, but it's not working.
Any advice to get me in the right direction on how to implement this would be appreciated.
Please specifically see the return statement and how I added the Link routers.
import React, { useState, useEffect } from 'react';
import './../App.css';
import * as ReactBootStrap from 'react-bootstrap';
import {Link} from 'react-router-dom';
function Shop() {
const [products, setProducts] = useState([]);
const [filterProducts, setFilteredProducts] = useState([]);
const [item, setItem] = useState('');
const [currentSort, setCurrentSort] = useState('');
const [loading, setLoading] = useState(false);
useEffect(async () => {
fetchItems();
}, [])
const fetchItems = async () => {
const data = await fetch('https://fakestoreapi.com/products');
const items = await data.json();
setProducts(items)
setLoading(true)
}
function priceUSD(change){
return change.toFixed(2)
}
useEffect(() => {
const filteredItems = products.filter((a) => {
if (item === '') {return a} else {return a.category === item}
});
setFilteredProducts(filteredItems);
}, [item, products])
useEffect(() => {
if (currentSort === '') {
return
}
const sortedItems = filterProducts.sort((a, b) => {
return currentSort === 'ASE' ? a.price - b.price : b.price - a.price
});
setFilteredProducts([...sortedItems]);
}, [currentSort])
return (
<div>
<div className="itemSort">
<p onClick={() => setItem("")}>All items</p>
<p onClick={() => setItem("men clothing")}>Men clothing</p>
<p onClick={() => setItem("women clothing")}>Women clothing</p>
<p onClick={() => setItem("jewelery")}>Jewelery</p>
<p onClick={() => setItem("electronics")}>Electronics</p>
</div>
<div className="itemSort">
<p>Order by price</p>
<p onClick={() => setCurrentSort('DESC')}>Highest</p>
<p onClick={() => setCurrentSort('ASE')}>Lowest</p>
</div>
<div className="gridContainer">
{loading ? <Link to="/Item">
(filterProducts.map((a, index) => (
<div key={index} className="productStyle">
<img src={a.image} className="productImage"></img>
<p>{a.title}</p>
<p>${priceUSD(a.price)}</p>
</div>
))) </Link> : (<ReactBootStrap.Spinner className="spinner" animation="border" />)
}
</div>
</div>
)
}
export default Shop;
You need a route like /item/:id to have one page for one item and assuming that a product has an id:
<div className="gridContainer">
{loading ?
(filterProducts.map((a, index) => (
<Link to={`/Item/${a.id}`}>
<div key={index} className="productStyle">
<img src={a.image} className="productImage"></img>
<p>{a.title}</p>
<p>${priceUSD(a.price)}</p>
</div>
</Link>
:
(<ReactBootStrap.Spinner className="spinner" animation="border" />)
}
</div>
I am doing the implementation of list pagination through a custom hook. The handleSetCurrentPage() function gets the correct number, it uses setCurrentPage(number). Consolelog setCurrentPage(number) showed undefined.
if you do all the same code only within one file (put everything in ListOfItems) it works fine.
Hook:
export const usePagination = (users = [], defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [currentUsers, setCurrentUsers] = useState([]);
const [amountOfPages, setAmountOfPages] = useState(0);
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, []);
const updateUsers = () => {
const indexOfLastPost = currentPage * amountPerPage;
const indexOfFirstPost = indexOfLastPost - amountPerPage;
const updatedUsers = users.slice(indexOfFirstPost, indexOfLastPost);
setCurrentUsers(updatedUsers);
};
const updateAmountOfPages = () => {
const updatedAmount = Math.ceil(users.length / amountPerPage);
setAmountOfPages(updatedAmount);
};
return {
setCurrentPage,
amountOfPages,
currentUsers,
};
};
list of items:
export function ListOfItems() {
const users = useSelector(state => state);
const { setCurrentPage, currentUsers, amountOfPages } = usePagination(users);
let {url} = useRouteMatch();
let items = currentUsers.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}, {index}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
{/*<form className="card">*/}
{/* <Search setSearch={setSearch} />*/}
{/*</form>*/}
<div>{items}</div>
<div>
<Pagination amountOfPages={amountOfPages} setCurrentPage={setCurrentPage}/>
</div>
</div>
)
}
pagination component:
const Pagination = ({amountOfPages, setCurrentPage}) => {
const [pageNumbers, setPageNumbers] = useState([]);
useEffect(() => {
calculatePageNumbers();
}, [amountOfPages]);
function calculatePageNumbers() {
const updatedPageNumbers = [];
for (let i = 1; i <= amountOfPages; i++) {
updatedPageNumbers.push(i);
}
setPageNumbers(updatedPageNumbers);
}
function handleSetCurrentPage(number) {
console.log(number);
return console.log(setCurrentPage(number));
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<button
onClick={() => handleSetCurrentPage(number)}
type="button"
className="page-link"
>
{number}
</button>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, [currentPage]);
Using React.js, When I change between filters buttons, I want pagination get back to the first page (number one).
This is my code in pagination component:
import React, { useState } from 'react'
const Pagination = ({productsPerPage, totalPosts, paginate}) => {
const [currentPage, setCurrentPage] = useState(1)
const PageNumbers =[]
const int = Math.ceil(totalPosts / productsPerPage)
for (let i = 1; i<= int; i++) {
PageNumbers.push(i)
}
return (
<nav className="">
<ul className="pagination">
<li className={currentPage === 1 ? 'disabled' : ''}>
<a onClick={() =>{setCurrentPage(currentPage - 1); paginate(currentPage - 1);}}>Previous</a>
</li>
{PageNumbers.map(number => (
<li
key={number}
className={number === currentPage ? "page-item active" : "page-item "}
>
<a
onClick={() => paginate(number)}
href="!#"
className="page-link "
>
{number}
</a>
</li>
))}
<li className={currentPage === int ? 'disabled' : ''}>
<a onClick={() => {setCurrentPage(currentPage + 1); paginate(currentPage + 1); }}>Next</a>
</li>
</ul>
</nav>
)
}
export default Pagination
This is the main App
const App = () => {
const [itemsAmount] = useState(100);
const [fromProduct, setFromProduct] = useState(1);
const [keys, setKeys] = useState([]);
const [products, setProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [productsPerPage, setProductsPerPage] = useState(10);
useEffect(() => {
axios('The API link')
.then(res => {
setProducts(res.data.itemsList);
res.data.itemsList[0] &&
setKeys(Object.keys(res.data.itemsList[0]).map((key, index) => key));
})
}, [Organization, HardWareStatuses, hardvarutyp, fromProduct,
itemsAmount,setProductsPerPage]);
/* Get current products*/
const indexOfLastProduct = currentPage * productsPerPage;
const indexOfFirstProduct = indexOfLastProduct - productsPerPage;
const currentProducts =
products.slice(indexOfFirstProduct,indexOfLastProduct );
/* Change page */
const paginate = pageNumber => setCurrentPage(pageNumber)
return (
<div>
{/* Create pagination */}
<div className="">
<Pagination
productsPerPage={productsPerPage}
totalProducts={products.length}
paginate={paginate}
filters ={filters}
/>
</div>
</div>
<Products products={currentProducts} keys={keys} />
<ExportCSV products={products} keys={keys} />
</div>
);
};
export default App;
Your pagination component need to be aware about your filters, and I guess those are managed by a parent component. One easy way would be to add them as parameters to your component to make it aware of them.
<Pagination productsPerPage="productsPerPage" totalPosts="totalPosts" paginate="paginate" filters="filters"/>
This way, you can implement a listenner to run some code only when filters change, using the useEffect hook:
import React, { useState, useEffect } from 'react'
const Pagination = ({productsPerPage, totalPosts, paginate, filters}) => {
const [currentPage, setCurrentPage] = useState(1)
const PageNumbers =[]
const int = Math.ceil(totalPosts / productsPerPage)
useEffect(() => {
setCurrentPage(1);
}, [filters]);
for (let i = 1; i<= int; i++) {
PageNumbers.push(i)
}
return (
<nav className="">
...
</nav>
)
}
export default Pagination;