I have a simple app written in React 17.0.2 which takes values of grocery items from an object and calculates their total cost, displaying it dynamically using setState — up to a total of 10 of any one item. That part is all working fine (I think), but I can’t get my reset button to work.
I’ve spent hours Googling and I’ve tried using useEffect
but it didn’t update the values on clicking the reset button.
My code is as below (you can ignore the Tailwind classes; I can’t seem to get that to work in CodeSandbox).
import "./styles.css";
import { useState } from "react";
const groceryItems = [
{ id: 0, type: "Bread", price: 3, count: 0 },
{ id: 1, type: "Milk", price: 2.5, count: 0 },
{ id: 2, type: "Cheese", price: 17.5, count: 0 }
];
const MAX_ITEM_COUNT = 10;
export default function App() {
const [counters, setCounter] = useState(groceryItems);
const sum = groceryItems.reduce(
(acc, itemPrices) => acc itemPrices.count * itemPrices.price,
0
);
const decrementCounter = (item) => {
if (item.count === 0) {
return;
}
const newState = counters.map((counter) => {
if (counter.id === item.id) {
return { ...counter, count: item.count-- };
} else {
return counter;
}
});
setCounter(newState);
};
const incrementCounter = (item) => {
if (item.count === MAX_ITEM_COUNT) {
return;
}
const newState = counters.map((counter) => {
if (counter.id === item.id) {
return { ...counter, count: item.count };
} else {
return counter;
}
});
setCounter(newState);
};
const resetCounters = () => {
const newState = counters.map((counter) => {
return { ...counter, count: 0 };
});
setCounter(newState);
};
return (
<main className="first-of-type:pt-20 last-of-type:pb-56 space-y-10 mx-auto max-w-4xl">
<h1 className="text-xl text-center">Groceries</h1>
<div id="items-description" className="px-10 text-center">
Choose your quantity of items below.
<br />
You may select a maximum of {MAX_ITEM_COUNT} items of each type.
</div>
<div className="item-chooser flex flex-col border mx-10 md:mx-36 py-10 space-y-14">
{groceryItems.map((item) => (
<div
className="flex flex-col md:flex-row mx-20 text-center place-items-center md:place-items-start"
key={item.id}
>
<div className="item-type sm:w-1/3 md:text-right">{item.type}</div>
<div className="item-price sm:w-1/3 md-text-left">
{"$" item.price.toFixed(2)}
</div>
<div className="buttons-counter flex flex-row text-center sm:w-1/4 md:ml-10 space-x-3">
<button
className="button decrement sm:w-1/3"
onClick={() => decrementCounter(item)}
>
-
</button>
<div className="item-quantity sm:w-1/3"> {item.count} </div>
<button
className="button increment sm:w-1/3"
onClick={() => incrementCounter(item)}
>
</button>
</div>
</div>
))}
<div className="flex flex-row mx-20">
<div className="spacer sm:w-1/3"></div>
<div className="spacer sm:w-1/3"></div>
<div className="reset-button flex sm:w-1/4 md:ml-10 space-x-3 m-auto text-center">
<div className="spacer sm:w-1/3"></div>
<button className="reset sm:w-1/3" onClick={resetCounters}>
Reset
</button>
<div className="spacer sm:w-1/3"></div>
</div>
</div>
<div className="flex flex-row mx-20 space-x-5 place-items-center">
<div className="flex-auto uppercase font-bold text-right">Total</div>
<div className="flex-auto">${sum.toFixed(2)}</div>
</div>
</div>
</main>
);
}
CodePudding user response:
As @nullptr has pointed, you should use the state in your map function for rendering the content, but I would also like to explain why your code was working for increment, decrement up till now.
return { ...counter, count: item.count };
During incrementing and decrementing you are using post increment/decrement operators. If you do item.count 1 instead of item.count , you will see that increment will stop working!
The item referred to in the code is the grocery item object.
By using post increment operator on item.count, you have mutated the count variable in the object. Causing the counts in the rendering
content to work correctly.Reset is not working because you are changing the count variable in
your state and not the grocery items which is used to render it.