I have these products with product variations of colors. The problem is that it does not record the product’s color that I have added.
For example, the shirt has 2 colors red and green, and I’ll choose red, only the red will be saved and the quantity will be updated for that product as a whole but not on the color.
This is what happens:
Codesandbox: https://codesandbox.io/s/add-to-cart-jsd9fd?file=/src/App.js:0-2599
Codes:
export default function App() {
const [cartItems, setCartItems] = useState([]);
const handleAdd = (id, name, size, cat, color) => {
console.log("add", id, name, size, cat, color);
const productExist = cartItems.find((item) => item.id === id);
if (productExist) {
setCartItems(
cartItems.map((item) =>
item.id === id
? { ...productExist, quantity: productExist.quantity + 1 }
: item
)
);
} else {
setCartItems([...cartItems, { id, name, size, cat, color, quantity: 1 }]);
}
};
// const handleAdd = (item) => {
// console.log("add", item);
// };
console.log(cartItems);
return (
<div className="App">
{products.map((index) => (
<ul key={index.id}>
<li>
<b>{index.prodName}</b>
</li>
<li>Category: {index.cat}</li>
<li>Size: {index.size}</li>
{Object.entries(index.colorMap).map((color) => (
<>
{color[1] !== 0 ? (
<>
{color[0]}
<button
onClick={(e) =>
handleAdd(
index.id,
index.prodName,
index.size,
index.cat,
color[0]
)
}
>
Add to Cart
</button>
{/* <button onClick={(e) => handleAdd(index)}>Add to Cart</button> */}
</>
) : (
<>2</>
)}
<br />
</>
))}
Php {index.price}.00
</ul>
))}
<Cart cartItems={cartItems} />
</div>
);
}
When you check if the product is already in the cart, you only check by its id:
const productExist = cartItems.find((item) => item.id === id);
But if you want to separate the products by id and color then you need to check for that too:
const productExist = cartItems.find((item) => item.id === id && item.color === color);
And of course update the same comparison logic when updating the cart:
setCartItems(
cartItems.map((item) =>
(item.id === id && item.color === color) // <--- here
? { ...productExist, quantity: productExist.quantity + 1 }
: item
)
);
Related
I have a button that trigger decreaseQuantity function and I have a state,checkoutList comes from context API. When I click that button, its directly decrease the quantity to 1, and what I want is only decrease by one. Why it's not decreasing one by one?
const { checkoutList, setCheckoutList } = useContext(AppContext);
const [uniqueCheckoutList, setUniqueCheckoutList] = useState([]);
const decreaseQuantity = (item) => {
let updatedList = [...uniqueCheckoutList];
let productIndex = updatedList.findIndex((p) => p.id === item.id);
updatedList[productIndex].quantity -= 1;
if (updatedList[productIndex].quantity === 0) {
updatedList = updatedList.filter((p) => p.id !== item.id);
}
setUniqueCheckoutList(updatedList);
setCheckoutList(updatedList);
localStorage.setItem("checkoutList", JSON.stringify(updatedList));
};
Edit: I have a useEffect hook that updates uniqueCheckoutList if the product has the same id. Add quantity property to it so it will just display once. This way I can show the quantity of that product next to the buttons in a span.
useEffect(() => {
let updatedList = [];
checkoutList.forEach((item) => {
let productIndex = updatedList.findIndex((p) => p.id === item.id);
if (productIndex === -1) {
updatedList.push({ ...item, quantity: 1 });
} else {
updatedList[productIndex].quantity += 1;
}
});
setUniqueCheckoutList(updatedList);
}, [checkoutList]);
Here I call the decreaseQuantity function.
<div className="checkout">
{uniqueCheckoutList.length === 0 ? (
<div>THERE IS NO ITEM IN YOUR BAG!</div>
) : (
uniqueCheckoutList.map((item) => {
return (
<div className="container" key={item.id}>
<div>
<img src={item.images[0]} alt={item.title} />
</div>
<div className="texts">
<h1>{item.title} </h1>
<p>{item.description} </p>
<button onClick={() => increaseQuantity(item)}>+</button>
<span>{item.quantity}</span>
<button onClick={() => decreaseQuantity(item)}>-</button>
</div>
</div>
);
})
)}
</div>
I have these cart items where there could be the same products with multiple colors in the cart. Like this:
How can I display it where it will display the product name, size, and category once, then below it are the different colors along with their quantity. Something like this:
Product ID
Tumbler 500 ML
Green : 4 (button to add and remove product) and the total
Pink : 5 (button to add and remove product) and the total
Black: 6 (button to add and remove product) and the total
Any help would be appreciated. Thank you.
Codesandbox: https://codesandbox.io/s/add-to-cart-sampled-2-efqrhd?file=/src/Cart.js:67-1764
const Cart = ({ cartItems, handleCartClearance, handleRemove, handleAdd }) => {
console.log(cartItems, "items");
const totalAmount = cartItems.reduce(
(price, item) => price + item.quantity * item.price,
0
);
const handleSubmit = (e) => {
e.preventDefault();
console.log(cartItems, "order");
};
return (
<div>
<form onSubmit={handleSubmit}>
Order page
{cartItems.length >= 1 && (
<Button onClick={handleCartClearance}>Clear Orders</Button>
)}
{cartItems.length === 0 && <div>No Items in the cart</div>}
<div>
{cartItems.map((item) => (
<div key={item.id + item.color}>
<li>{item.id}</li>
<li>{item.name + " " + item.size + "" + item.cat}</li>
<li>{item.color}</li>
<li>{item.quantity}</li>
<li>Total: {Number(item.quantity) * Number(item.price)}</li>
<button
onClick={(e) =>
handleAdd(
item.id,
item.prodName,
item.price,
item.size,
item.cat,
item.color
)
}
>
+
</button>
<button onClick={() => handleRemove(item)}>- </button>
</div>
))}
-----------------------------------------------------
<div>
<b>Total Amount :{totalAmount}</b>
</div>
{cartItems.length >= 1 && <Button type="submit">Save Order</Button>}
</div>
</form>
</div>
);
};
export default Cart;
Try this
codesandbox: https://codesandbox.io/s/add-to-cart-sampled-2-forked-zzcnpf
{Object.entries(
cartItems.reduce((prev, item) => {
if (!prev[item.id]) prev[item.id] = { ...item, nest: [] };
prev[item.id].nest.push(item);
return prev;
}, {})
).map(([id, obj], idx) => (
<div key={id + obj.color}>
<li>{obj.id}</li>
<li>{obj.name + " " + obj.size + "" + obj.cat}</li>
{obj.nest.map((nest, idx) => (
<React.Fragment key={idx}>
<li>{nest.color}</li>
<li>{nest.quantity}</li>
<li>Total: {Number(nest.quantity) * Number(nest.price)}</li>
<button
onClick={(e) =>
handleAdd(
nest.id,
nest.prodName,
nest.price,
nest.size,
nest.cat,
nest.color
)
}
>
+
</button>
<button onClick={() => handleRemove(nest)}>- </button>
</React.Fragment>
))}
</div>
))}
I made a photo gallery with a filter for categories, but I want to make the active category button to have the active class
I display the buttons by mapping through the galleryData array
{
galleryData.map((item, index) => (
<button
key={index}
onClick={() => filterGalley(item.category)}
className="filter-button"
>
{item.category}
</button>
));
}
And onClick I filter the gallery items by category. The value is a string of category type
const filterGalley = (value) => {
if (value === "all") {
setGalleryItems(galleryData);
return;
}
const filteredData = galleryData.filter((item) => item.category === value);
console.log(value);
setGalleryItems(filteredData);
};
How can I pass the active class to the currently viewed category? onMount should be all and after that the one that's clicked.
Define a state for activeCategory and use it for active class:
const [activeCategory, setActiveCategory] = useState('all');
const filterGalley = (value) => {
setActiveCategory(value);
if (value === 'all') {
setGalleryItems(galleryData);
return;
}
const filteredData = galleryData.filter(
(item) => item.category === value
);
console.log(value);
setGalleryItems(filteredData);
};
And use it:
{galleryData.map((item, index) => (
<button
key={index}
onClick={() => filterGalley(item.category)}
className={"filter-button " + (activeCategory === item.category ? 'active' : '')}
>
{item.category}
</button>
))}
I have a list of buttons and they are multi selectable. when I select the buttons I want to add , it will be added to the array perfectly and turned to blue and when I click on a already selected button it should be get removed from the array and turned to white but it doesn't. Below shows what I tried so far.
The first array (products) is to save the API data. Second one is to save the selected products.
const [products, setProducts] = useState([]);
const [selectedProducts, setselectedProducts] = useState<any>([]);
{products.length !== 0 ? (
products?.map(
(item: any, index) => (
<SButton
key={item.key}
label={item.value}
onClick={() => {
selectedProducts(item);
}}
isSelected={item.selected === "YES"}
/>
)
)
) : (
<p>No products</p>
)}
function selectedProducts(item:any){
if(selectedProducts.length !== 0){
selectedProducts.map((selecteditem:any)=>{
if(selecteditem.key == item.key ){
item.selected = "NO";
setselectedProducts(selectedProducts.filter((item: any )=> item.key !== selecteditem.key))
}else{
item.selected = "YES";
setselectedProducts([...selectedProducts, item]);
}
})
}else{
setselectedProducts([...selectedProducts, item]);
item.selected = "YES";
}
}
How about something like this?
const [products, setProducts] = useState([]);
const [selectedProduct, setSelectedProduct] = useState();
{(products.length > 0) ? (
<Fragment>
{products.map((item)=>{
const {key, value, selected } = item;
return (
<SButton
key={key}
label={value}
onClick={() => {
setSelectedProduct(item);
const newState = !selected;
products.forEach((p)=>{
if (p.key === item.key) p.selected = newState;
});
setProducts([...products]);
}}
isSelected={selected}
/>
);
})}
</Fragment>
): (
<p>No products</p>
)}
First, you are using selectedProducts both as function name and name of selected products state.
Second, you should not assign values to item. Use spread operator instead.
Also, you can access the previous state from setState instead of using state directly.
function removeFromSelectedProducts(item: any) {
// Set selected to 'NO' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'NO' } : product
)
)
// Remove product from selectedProducts
setSelectedProducts((prevSelectedProducts) =>
prevSelectedProducts.filter((product) => product.key !== item.key)
)
}
function addToSelectedProducts(item: any) {
// Set selected to 'YES' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'YES' } : product
)
)
// Add item to selectedProducts
setSelectedProducts((prevSelectedProducts) => [...prevSelectedProducts, { ...item, selected: 'YES'}])
}
function selectProduct(item: any) => {
if (selectedProducts.some((product) => product.key === item.key)) {
removeFromSelectedProducts(item)
} else {
addToSelectedProducts(item)
}
}
You can simplify this using useReducer hook instead of using separate functions for addition & removal of selected products.
I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>