React toggle active class between button group array - javascript

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>
))}

Related

Why does this function reduce to one rather than decrease one?

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>

How can I save the multiple product variation?

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
)
);

onclick react, I want my button to change color

There is more than one button using the map function and display it. Want to change the Background color of a button that I click. And others want it as it is. And again when I click on another button then only change the BG color of that button only.
This is what I have already:
const handleClick = (option, index) => {
const nextQuestion = currentQuestion + 1;
if (data[currentQuestion].answer === option) {
// setChangeColor(isCorrect);
isCorrect = "bg-success text-white";
setTimeout(() => {
setCurrentQuestion(nextQuestion);
setMoneyLadder(moneyLadder - 1);
setChangeColor("");
}, 2000);
} else {
// setChangeColor(isIncorrect);
isIncorrect = "bg-danger";
setTimeout(() => {
window.location.reload();
setCurrentQuestion(0);
setMoneyLadder(15);
setChangeColor("");
}, 2000);
}
};
<div className="answers">
{data[currentQuestion].options.map(
(option, index) => (
<button
className={
// data[currentQuestion].answer ===
// option
// ? "text-success"
// : ""
}
key={index}
onClick={() =>
handleClick(option, index)
}
dangerouslySetInnerHTML={{
__html: `${index + 1}) ${option}`,
}}
></button>
)
)}
</div>
Any idea?
You could initialize a state variable with an array of bool (buttonSelected). Then, on handleClick with index you intercept the button clicked and toggle the boolean. In button's style you could write:
{{backgroundColor: buttonSelected[index] ? 'red':'green'}}
So the backgroundColor becomes red if button is clicked, green otherwise:
const [buttonSelected, setButtonSelected] = useState(new Array(data[currentQuestion].options.length).fill(false));
const handleClick = (index) => {
let result = [...buttonSelected];
result[index] = !result[index];
setButtonSelected(result);
}
<div className="answers">
{data[currentQuestion].options.map(
(option, index) => (
<button
className={
// data[currentQuestion].answer ===
// option
// ? "text-success"
// : ""
}
style={{backgroundColor: buttonSelected[index] ? 'red':'green'}}
key={index}
onClick={() =>
handleClick(index)
}
dangerouslySetInnerHTML={{
__html: `${index + 1}) ${option}`,
}}
></button>
)
)}
</div>

React material ui select all checkbox

I need to make that when selecting checkbox Select All, the rest of the checkboxes from the list are checked, and each checkbox can be selected separately. But when select one of the checkboxes does not checked the previous checkbox.
sandbox
const options = [ 'Selected Item 1', 'Selected Item 2', 'Selected Item 3'];
export default function App() {
const [selected, setSelected] = useState([]);
const isAllSelected =
options.length > 0 && selected.length === options.length;
const handleChange = (event) => {
const value = event.target.value;
console.log(value)
if (value === "all") {
setSelected(selected.length === options.length ? [] : options);
return;
}
setSelected(value);
};
const listItem = options.map((option) => {
return (
<div key={option}>
<Checkbox
value={option}
onChange={handleChange}
checked={selected.includes(option) } />
<span>{option}</span>
</div>
)
})
return (
<div style={{ display: 'flex', alignItems: 'center', margin: 10 }}>
<Checkbox
value='all'
onChange={handleChange}
checked={isAllSelected}
/>
<span> Select All</span>
{listItem}
</div>
);
}
When you click on checkboxes other than "all" checkbox, you are just setting your selected state to that value: setSelected(value);. There are 2 problems with this code - 1) In your case the selected state should always be an array, but you are changing that to a string. 2) you are disregarding any previous values that might be added to the selected state. You must figure out if the clicked checkbox was already in the selected state or not. if not, you should add it to the state. If it was then you should remove it from the state. Here is how you can do this:
const handleChange = (event) => {
const value = event.target.value;
console.log(value)
if (value === 'all') {
setSelected(selected.length === options.length ? [] : options);
return;
}
if (selected.indexOf(value) !== -1) { // if value already present
const newSelected = selected.filter((s) => s !== value );
setSelected(newSelected);
} else { // if value not present
setSelected([ ...selected, value ]);
}
}
You can make a data-structure like [{data:any, selected: boolean}, {data:any, selected: boolean}]
Then you loop through it and display them as
const [items, setItems] = useState<Asset[]>([]);
const [select, setSelect] = useState(false)
const handleChange = (index, checked) => {
if(select && !checked) setSelect(false)
const itemRef = [...items];
itemRef[index].selected = checked
setItems([...itemRef]);
}
const handleChangeAll = (checked) => {
const itemRef = [...items];
itemRef.forEach((_item, i) => {
itemRef[i].selected = checked
})
setSelect(checked)
setItems([...itemRef]);
}
------
All selection checkbox
<Checkbox
checked={select}
onChange={(_e, checked) => handleChangeAll(checked)}
/>
** items **
items.map((item, i) => (
<Checkbox
key={item.data.id}
checked={item.selected}
onChange={(_e, checked) => handleChange(index, checked)}
/>
))
Once it is done if you want to extract the selected data, you can just apply the filter method based on item.selected from items array,
Need to update selected options array instead of assigning the value directly inside handleChange method. So first check if selected list contains the checked option if it contains then remove it, otherwise add it to the selected list.
const handleChange = (event) => {
const value = event.target.value;
console.log(value);
if (value === "all") {
setSelected(selected.length === options.length ? [] : options);
return;
}
// added below code to update selected options
const list = [...selected];
const index = list.indexOf(value);
index === -1 ? list.push(value) : list.splice(index, 1);
setSelected(list);
};
Sandbox link here

How to remove a object from an array in react hook

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.

Categories

Resources