I have an array of 12 objects of a list of movies and i want to create pagination for them using React paginate by displaying 4 items for each page, I already display the items on the UI but the numbers of pages didn't work even when I click on the Next button.
it is my first experience with pagination in react.
import './App.css';
import React,{useState} from 'react';
import MoviesData from './components/Data/MoviesData';
import ReactPaginate from 'react-paginate';
function App() {
const [categories, setCategories] = useState(allCategories);
const [moviesList, setMoviesList] = useState(MoviesData.slice(0, 4));
const [pageNumber, setPageNumber] = useState(0);
const moviePerPage = 4;
//to determinate how many pages we going to have
const pageCount = Math.ceil(moviesList.length / moviePerPage);
const changePage = ({ selected }) => {
setPageNumber(selected);
};
return (
<main>
<MoviesCards moviesList ={moviesList} removeMovie={removeMovie} />
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
disabledClassName={"paginationDisabled"}
activeClassName={"paginationActive"}
/>
</main>
)
}
export default App;
This component where i display my items :
import React from 'react';
import SingleMovieCard from '../SingleMovieCard/SingleMovieCard';
const MoviesCards = ({moviesList}) => {
return (
<section>
<div className="title">
<h2>Welcome to the box office</h2>
</div>
<div >
{moviesList.map((singleMovie)=> {
return(
<SingleMovieCard singleMovie={singleMovie}
key={singleMovie.id}
removeMovie={removeMovie} />
)
})}
</div>
</section>
)
}
export default MoviesCards;
There is some problems. I will list it here:
const pageCount = Math.ceil(moviesList.length / moviePerPage);
should be:
const pageCount = Math.ceil(MoviesData.length / moviePerPage);
That's because your page count is relative to whole movie count not only the page count, which is your state storing
Another problem is with your page management, the first is ok, however every time you change your page you need to set it with the new list of movies. That means you need an useEffect to track changes in pageNumber and then set your moviesList, something like this:
useEffect(() => {
setMoviesList(MoviesData.slice(pageNumber * 4, (pageNumber + 1) * 4));
}, [pageNumber]);
I've done a simple case:
import React, { useEffect, useState } from "react";
import ReactPaginate from "react-paginate";
const MoviesData = [
{ title: "Foo", category: "horror" },
{ title: "Foo1", category: "horror" },
{ title: "Foo2", category: "horror" },
{ title: "Foo3", category: "horror" },
{ title: "Bar", category: "horror" },
{ title: "Bar1", category: "horror" },
{ title: "Bar2", category: "horror" },
{ title: "Bar3", category: "horror" }
];
const SingleMovieCard = ({ singleMovie }) => {
return <div>{singleMovie.title}</div>;
};
const MoviesCards = ({ moviesList }) => {
return (
<section>
<div className="title">
<h2>Welcome to the box office</h2>
</div>
<div>
{moviesList.map((singleMovie) => {
return (
<SingleMovieCard singleMovie={singleMovie} key={singleMovie.id} />
);
})}
</div>
</section>
);
};
function App() {
const [pageNumber, setPageNumber] = useState(0);
const [moviesList, setMoviesList] = useState(MoviesData.slice(0, 4));
const moviePerPage = 4;
const pageCount = Math.ceil(MoviesData.length / moviePerPage);
useEffect(() => {
setMoviesList(MoviesData.slice(pageNumber * 4, (pageNumber + 1) * 4));
}, [pageNumber]);
const changePage = ({ selected }) => {
console.log(selected);
setPageNumber(selected);
};
return (
<main>
<MoviesCards moviesList={moviesList} />
<ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
pageCount={pageCount}
onPageChange={changePage}
containerClassName={"paginationBttns"}
previousLinkClassName={"previousBttn"}
nextLinkClassName={"nextBttn"}
disabledClassName={"paginationDisabled"}
activeClassName={"paginationActive"}
/>
</main>
);
}
export default App;
Related
Facing a strange issue with the latest stable releases of Nextjs and React, where state and view updates are out-of-sync.
Example (v1): using useMemo
import { useState, useMemo } from "react";
const items = [
{ id: 1, name: "apple" },
{ id: 2, name: "orange" },
{ id: 3, name: "mango" }
];
export default function IndexPage() {
const [desc, setDesc] = useState(true);
const sortedItems = useMemo(() => [...(desc ? items : items.reverse())], [
desc
]);
return (
<div>
<button onClick={() => setDesc((s) => !s)}>change order</button>
<br />
<span>desc: {desc.toString()}</span>
<pre>{JSON.stringify(sortedItems, null, 2)}</pre>
</div>
);
}
Example (v2): using useState
import { useState, useMemo } from "react";
const items = [
{ id: 1, name: "apple" },
{ id: 2, name: "orange" },
{ id: 3, name: "mango" }
];
export default function IndexPage() {
const [state, setState] = useState({ desc: true, items });
function handleSort() {
setState((s) =>
s.desc
? { desc: false, items: [...items.reverse()] }
: { desc: true, items: [...items] }
);
}
return (
<div>
<button onClick={handleSort}>change order</button>
<br />
<span>desc: {state.desc.toString()}</span>
<pre>{JSON.stringify(state.items, null, 2)}</pre>
</div>
);
}
Package versions:
"next": "12.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
As you can see from the output below, array elements are not updated on the view front based on the current sort state (in both the examples). Multiple clicks are required to do so.
Is this a random bug or am I fooling myself!?
CodeSandbox - Contains both code examples
Calling .reverse on a list will mutate it. Then wherever you reference that list again, it will be reversed.
You don't need to copy the items on to state either, you only need the isReversed state.
const Comp = () => {
const [isReversed, setIsReversed] = useState(false);
const toggle = () => setIsReversed(r => !r);
const list = useMemo(() => isReversed ? [...items].reverse() : items, [isReversed]);
// use list
}
From the useState example, separating the states for checking if it has been sorted and state for holding the items would be a great start.
import { useState } from "react";
const items = [
{ id: 1, name: "apple" },
{ id: 2, name: "orange" },
{ id: 3, name: "mango" },
];
export default function IndexPage() {
const [areSorted, setAreSorted] = useState(false);
const [itemsArr, setItemsArr] = useState(items);
function handleSort() {
setAreSorted(!areSorted);
setItemsArr(itemsArr.reverse());
}
return (
<div>
<button onClick={handleSort}>change order</button>
<br />
<span>{"Desc : " + areSorted}</span>
<pre>{JSON.stringify(itemsArr, null, 2)}</pre>
</div>
);
}
The main problem right now is sharing the quantity of each item to the cart. I've read that I have to lift the state up to App.js to achive that, but then I have no clue how to create individual quantity states for each item (Right now they are created when Item component is being rendered. I tried using .map() on items (in App.js), but it doesn't work and gives an error.
Here's some code:
App.js
import { useState } from "react";
import Header from "./Components/Header";
import Cart from "./Components/Cart";
import Shop from "./Components/Shop";
import img1 from "./Pictures/food1.jpg";
import img2 from "./Pictures/food2.jpg";
import img3 from "./Pictures/food3.jpg";
function App() {
const [showCart, setShowCart] = useState(false);
const [items, setItems] = useState([
{
id: 1,
name: "Spaghetti",
price: "300$",
img: img1,
alt: "Plate of Spaghetti",
},
{
id: 2,
name: "Pizza",
price: "500$",
img: img2,
alt: "Pizza",
},
{
id: 3,
name: "Tiramisu",
price: "150$",
img: img3,
alt: "Tiramisu",
},
]);
// let [quantity, setQuantity] = useState(0);
return (
<div className="container">
<Header onShowCart={() => setShowCart(!showCart)} />
{showCart && <Cart items={items} />}
<Shop items={items} />
</div>
);
}
export default App;
Shop.js
import Item from "./Item";
const Shop = ({ items}) => {
return (
<div className="shop">
{items.map((item) => (
<Item
key={item.id}
item={item}
/>
))}
</div>
);
};
export default Shop;
Item.js (Here's the [quantity, setQuantity] = useState(0) which I have to lift up.)
import { useState } from "react";
import ItemInfo from "./ItemInfo";
const Item = ({ item }) => {
let [quantity, setQuantity] = useState(0);
const addToCart = () => {
console.log("Add Value");
setQuantity(quantity + 1);
console.log(quantity);
};
const removeFromCart = () => {
console.log("Lower Value");
quantity > 0 && setQuantity(quantity - 1);
console.log(quantity);
};
return (
<div className="item">
<img className="image" src={item.img} alt={item.alt} />
<ItemInfo
name={item.name}
price={item.price}
onAdd={addToCart}
onLower={removeFromCart}
quantity={quantity}
/>
</div>
);
};
export default Item;
You can reuse items for quantity state and then create another function call updateQuantity and then pass it down to the child component Shop
import { useState } from "react";
import Header from "./Components/Header";
import Cart from "./Components/Cart";
import Shop from "./Components/Shop";
import img1 from "./Pictures/food1.jpg";
import img2 from "./Pictures/food2.jpg";
import img3 from "./Pictures/food3.jpg";
function App() {
const [showCart, setShowCart] = useState(false);
const [items, setItems] = useState([
{
id: 1,
name: "Spaghetti",
price: "300$",
img: img1,
alt: "Plate of Spaghetti",
quantity: 0, //quantity state
},
{
id: 2,
name: "Pizza",
price: "500$",
img: img2,
alt: "Pizza",
quantity: 0, //quantity state
},
{
id: 3,
name: "Tiramisu",
price: "150$",
img: img3,
alt: "Tiramisu",
quantity: 0, //quantity state
},
]);
const updateQuantity = (updatedItem) => {
const updatedItems = items.map(item => updatedItem === item ? {...updatedItem} : item) //update quantity from `updatedItem`
setItems(updatedItems)
}
return (
<div className="container">
<Header onShowCart={() => setShowCart(!showCart)} />
{showCart && <Cart items={items} />}
<Shop items={items} updateQuantity={updateQuantity}/>
</div>
);
}
export default App;
Note that if items are controlled by API calls, you don't need to add quantity: 0 directly on items
Passing updateQuantity down to Shop and
import Item from "./Item";
const Shop = ({ items, updateQuantity }) => {
return (
<div className="shop">
{items.map((item) => (
<Item
key={item.id}
item={item}
updateQuantity={updateQuantity}
/>
))}
</div>
);
};
export default Shop;
The last piece is updating your Item component logic
import { useState } from "react";
import ItemInfo from "./ItemInfo";
const Item = ({ item, updateQuantity }) => {
const addToCart = () => {
const currentQuantity = item.quantity || 0 //if `item.quantity` is undefined, it will use 0 as default value
item.quantity = currentQuantity + 1
updateQuantity(item) //lift `item` state to `App`
};
const removeFromCart = () => {
if(!item.quantity) { //0 or undefined
return //do nothing
}
item.quantity = item.quantity - 1
updateQuantity(item) //lift `item` state to `App`
};
return (
<div className="item">
<img className="image" src={item.img} alt={item.alt} />
<ItemInfo
name={item.name}
price={item.price}
onAdd={addToCart}
onLower={removeFromCart}
quantity={item.quantity || 0} //can access quantity directly from `item`
/>
</div>
);
};
export default Item;
Sorry for my bad english, i'm from Argentina.
I need to manage an array of checkboxes to make a filter. I'm working in a fictional rental car web (I'm still learning, it's a practice), and I want that when I check one or more checkboxes, it only show the cars with those characteristics. Car characteristics are un a .json file (api call).
This is the array of checkbox I've created to map and display in the layout.
export const features = [
{ name: "Manual Transmission", checked: false },
{ name: "5 seats", checked: false },
{ name: "Convertibles", checked: false },
{ name: "Automatic Transmission", checked: false },
{ name: "7 seats or more", checked: false },
];
And this is the component "Filter"
import { useContext, useEffect, useState } from "react";
import { v1 as uuidv1 } from "uuid";
import { features } from "../../utils/features";
import "./styles.css";
import { FilterContext } from "../../contexts/FilterContext";
import useInput from "../../hooks/useInput";
import { Carlist } from "../CarList";
export const Filter = () => {
const carsData = useContext(FilterContext);
const [filteredCars, setFilteredCars] = useState([]);
const [select, handleSelect] = useInput();
const [checkedValues, setCheckedValues] = useState([]);
const handleChecked = (e) => {
};
/* const featuresList = carsData.map((data) => data.Features2);
const featuresArray = Object.keys(featuresList).map(function (key) {
return featuresList[key];
}); */
useEffect(() => {
const filterSelect = carsData.filter((car) =>
car.VehGroup.includes(select)
);
setFilteredCars(filterSelect);
}, [select, carsData, checkedValues]);
return (
<div className="search-container">
<div className="filter-container">
<div className="filter-select-container">
<h2>Filter by</h2>
<select
className="filter-select"
onChange={handleSelect}
value={select}
>
<option value="">All</option>
{carsData.map((data) => (
<option key={uuidv1()} value={data.VehGroup}>
{data.VehGroup}
</option>
))}
</select>
</div>
<div className="filter-checkbox-container">
{features.map((carFeature) => {
return (
<div key={carFeature.name} className="filter-checkbox">
<input
onChange={handleChecked}
name={carFeature.feature}
type="checkbox"
/>
<label htmlFor={carFeature.feature}>{carFeature.name}</label>
</div>
);
})}
</div>
</div>
<Carlist carsData={filteredCars} />
</div>
);
};
CardList:
import "./styles.css";
import { v1 as uuidv1 } from "uuid";
import { CardCard } from "../CarCard";
export const Carlist = ({ carsData }) => {
return (
<div className="carList-container">
{carsData.map((data) => (
<CardCard
airConditionInd={data.AirConditionInd}
code={data.Code}
features2={data.Features2}
name={data.Name}
pictureURL={data.PictureURL}
rates={data.Rates}
vehGroup={data.VehGroup}
key={uuidv1()}
/>
))}
</div>
);
};
.JSON
https://api.npoint.io/3c713cdde915d38fd6aa
On your handledChecked function you should make it add the checked element to your filteredCars state by pushing the new value to this state.
setFilteredCars(oldArray => [...oldArray, newElement]);
newElement could be an object since your main array is array of objects.
import { Fragment, useState } from "react";
import Select from 'react-select';
let items = [
{
item: 1,
name: "tv"
},
{
item: 2,
name: "PC"
}
]
const Home = () => {
const [selectedValue, setSelectedValue] = useState(6)
const handleChange = obj => {
setSelectedValue(obj.item)
}
return (
<Fragment>
<div>Home page</div>
<p>Test React Select...</p>
<Select
value={items.find(x => x.item === selectedValue)}
options={items}
onChange={handleChange}
/>
<p>selected Value:...</p>
{selectedValue}
</Fragment>
)
}
export default Home;
you can pass the mapped array to "options" property:
options={items.map(({item, name}) => ({value: name, label: item}))}
I have an orders component that contains some orders each order has products and the user can update the quantity and price the problem is that the update process is very slow because if I update a product quantity for example all products in the order get remounted again and I think this is the main issue. if I have 100 products the product page render 100 times or more (one time for each product ) here is my current implementation on code sandbox: https://codesandbox.io/s/holy-tdd-3nj7g?file=/src/OrderingPage/Order/index.js
here is the order component that have multiple order but for simplicity lets assume we only have one order
import { useState, useCallback } from "react";
import Order from "./Order/index";
const OrderingScreen = () => {
const initialOrderData = {
order: {
total: 0,
vat: 0,
deliveryCharge: 0,
date: 0,
orderStart: 0,
orderEnd: 0,
customerGender: "",
actualPaid: 0,
dateStr: "",
payType: "cash",
itemsCount: 0,
orderDetails: [
{ name: "prod1", sellPrice: 120, quantity: 3 },
{ name: "prod2", sellPrice: 12, quantity: 2 },
{ name: "prod3", sellPrice: 1123, quantity: 2 },
{ name: "prod4", sellPrice: 1543, quantity: 1 },
{ name: "prod5", sellPrice: 123, quantity: 8 }
]
}
//other properties
};
const [ordersData, setOrdersData] = useState([initialOrderData]);
const resetOrder = useCallback(() => {
let ordersDataCopy = [...ordersData];
ordersDataCopy[0] = initialOrderData;
setOrdersData(ordersDataCopy);
}, [ordersData]);
const updateOrderProducts = useCallback(
(products) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"]["orderDetails"] = [...products];
setOrdersData(ordersCopy);
},
[ordersData]
);
const updateOrder = useCallback(
(order) => {
let ordersCopy = [...ordersData];
ordersCopy[0]["order"] = { ...order };
setOrdersData(ordersCopy);
},
[ordersData]
);
return (
<Order
order={ordersData[0].order}
products={ordersData[0].order.orderDetails}
updateOrderProducts={updateOrderProducts}
updateOrder={updateOrder}
resetOrder={resetOrder}
/>
);
};
export default OrderingScreen;
here is the single order component
import OrderItem from "./OrderItem";
import { useEffect, memo, useCallback } from "react";
const Order = ({ order, products, updateOrderProducts, updateOrder }) => {
const handleOrderChange = useCallback((propertyName, value) => {
let orderCopy = { ...order };
orderCopy[propertyName] = value;
updateOrder(orderCopy);
});
const deleteProduct = useCallback((index) => {
let productsCopy = [...products];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
updateOrderProducts(productsCopy);
}, []);
const handleOrderItemRemove = useCallback((index) => {
deleteProduct(index);
}, []);
const handleQuantityChange = useCallback((index, quantity) => {
let productsCopy = [...products];
productsCopy[index]["quantity"] = quantity;
updateOrderProducts(productsCopy);
}, []);
return (
<div className="d-flex px-2 flex-grow-1 mb-1">
{products.map((product, idx) => (
<OrderItem
product={product}
key={idx}
index={idx}
onRemove={handleOrderItemRemove}
onQuantityChange={handleQuantityChange}
updateProduct={handleOrderChange}
/>
))}
</div>
);
};
export default memo(Order);
and the last component which is the product component which I think is causing the performance issue (it render 1 + the number of products in the order if I update the quantity of one product )
import RemoveCircleIcon from "#mui/icons-material/RemoveCircle";
import AddCircleIcon from "#mui/icons-material/AddCircle";
import { memo, useMemo, useState, useEffect } from "react";
const OrderItem = ({ product, index, onQuantityChange }) => {
console.log("remount");
const [itemQuantity, setItemQuantity] = useState(product.quantity);
const incrementQuantity = () => {
onQuantityChange(index, itemQuantity + 1);
};
const decrementQuantity = () => {
itemQuantity > 1 && onQuantityChange(index, itemQuantity - 1);
};
useEffect(() => {
setItemQuantity(product.quantity);
}, [product.quantity]);
const productInfo = useMemo(() => (price, quantity, name) => {
let total = price * quantity;
total = +total.toFixed(2);
price = +price.toFixed(2);
return (
<div className={`col-9 col-xl-10 border rounded-start p-1 `}>
{name}
<div className="justify-content-around d-flex">
{"Price:" + price}
{" Quantity:" + quantity}
{" Total:" + total}
</div>
</div>
);
});
useEffect(() => {
setItemQuantity(product.quantity);
}, [product]);
const quantityColumn = (
<div>
<AddCircleIcon onClick={incrementQuantity} />
{itemQuantity}
<RemoveCircleIcon onClick={decrementQuantity} />
</div>
);
return (
<div style={{ marginBottom: "25px" }}>
{productInfo(product.sellPrice, product.quantity, product.name)}
{quantityColumn}
</div>
);
};
export default memo(OrderItem);
what I want to achieve is a snappy component update (maybe by making the product component mount only for the changed product)
you may see it fast on the sandbox but this version just explains the problem only... the real version is much complicated
You can improve performance by changing your React.memo components.
Instead of memo(OrderItem) pass as second argument function that will compare previous and current state:
function areEqualOrderItem(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return prevProps.quantity === nextProps.quantity;
}
export default memo(OrderItem, areEqualOrderItem);
Also I suggest do not use array index as component key try product name instead of this.
useCallback do nothing in your code. Instead you can use this one:
const handleOrderItemRemove = useCallback((index) => {
updateOrderProducts(prod => {
let productsCopy = [...prod];
productsCopy = productsCopy.filter(
(product) => product !== productsCopy[index]
);
return productsCopy;
});
}, [updateOrderProducts]);
const updateOrderProducts = useCallback(
(products) => {
setOrdersData(ords => {
let ordersCopy = [...ords];
ords[0]["order"]["orderDetails"] = [...products];
return ordersCopy;
});
},
[setOrdersData]
);
When you fix all your callbacks you can boost performance. At this time, your code cause rerender of all items almost every small change.