Reputation: 187
I'm having a problem with displaying a totalAmount
in my app. Because of a two-times rendering, it displays wrong value. I try to use useMemo hook for that.
This is how it looks like (don't bother with css :D )
So it counts like this:
7.99 * 2 + 4.99 = 20.97
the last added item -44.99
hasn't been counted in totalAmount
and it multiplies first item by 2
because of rendering twice, I guess
And here is my code sandbox: https://codesandbox.io/s/objective-kare-805kc?file=/src/components/App.tsx
Upvotes: 0
Views: 157
Reputation: 85012
The fact that your components are not resilient to multiple renders is itself a problem. In general, you cannot assume your component will only render a certain number of times, and this will become especially true when the upcoming Concurrent Mode is released. Reducing the number of renders may be useful for performance reasons, but i would not consider that a fix to your issue.
The issue appears to be that your reducer is impure. A reducer should be a pure function: it takes in a state and an action, and it produces a new state, with no side effects. But instead, in the middle of your reducer you're calling setFixedAmount
and setTotalAmount
, which will set state and cause a rerender.
From looking at your code, i think it is a mistake to have three pieces of state. You have products
, totalAmount
, and fixedAmount
, and are trying to write code to keep them all in sync with eachother. Instead a better approach is to just have one state: products
. totalAmount
and fixedAmount
are then derived values calculated based on products.
function App() {
const [allData] = useState(data.products);
const [products, dispatch] = useReducer((state: any, action: any): any => {
switch (action.type) {
case "ADD_PRODUCT":
state.filter((i: any) => {
if (i.id === action.payload.id) {
i.count = i.count + 1;
}
return i;
});
const noDuplicateArr = state.filter(
(i: any) => i.id !== action.payload.id
);
return [...noDuplicateArr, action.payload];
case "DELETE_PRODUCT":
state.filter((i: any) => {
if (i.id === action.payload.id) {
i.count = 1;
}
return i;
});
return state.filter((item: any) => item.id !== action.payload.id);
default:
return state;
}
}, []);
let totalAmount = 0;
products.forEach(product => {
totalAmount += product.count * product.price.amount;
})
const fixedAmount = totalAmount.toFixed(2);
const store = useMemo(() => ({ products, dispatch }), [products]);
return (
<div className="app">
<ProductContext.Provider value={{ allData, fixedAmount, store }}>
<ShoppingList />
<ShoppingBag />
</ProductContext.Provider>
</div>
);
}
Upvotes: 3