I'm working on a React project and using the useReducer
hook to manage my state. I have a specific issue with updating the fixed price for my items.
In my code, I'm trying to increment the quantity of items in the shopping cart, and with each increment, I want to add the fixed price to the total price. However, I'm facing a problem where the fixed price keeps updating constantly.
Here's my code:
import React, { createContext, useContext, useEffect, useReducer } from 'react';
import { FetchedOrders } from "../../api/fetch";
// Створюємо контекст
const OrdersContext = createContext();
const OrdersDispatchContext = createContext();
function ordersReducer(orders, action) {
switch (action.type) {
case "ORDERS":
return action.payload;
case "REMOVE_ORDER":
return orders.filter((order) => order.id !== action.id);
case "INCREMENT":
const { id } = action;
const updatedOrders = orders.map((order) => {
if (order.id === id) {
const newAmount = order.amount + 1;
const newPrice = parseFloat(order.price) + parseFloat(order.price);
return { ...order, amount: newAmount, price: newPrice };
} else {
return order;
}
});
return updatedOrders;
default: return orders
}
}
export function StateProvider({ children }) {
const [orders, dispatch] = useReducer(ordersReducer, null);
useEffect(() => {
const fetching_orders = async () => {
const data = await FetchedOrders();
dispatch({ type: 'ORDERS', payload: data});
};
fetching_orders();
}, [])
return (
<OrdersContext.Provider value={{ orders }}>
<OrdersDispatchContext.Provider value={{ dispatch }}>
{children}
</OrdersDispatchContext.Provider>
</OrdersContext.Provider>
);
};
export function useOrdersValues() {
return useContext(OrdersContext);
}
export function useOrdersDispatch() {
return useContext(OrdersDispatchContext);
};
import Card from '../card/card.jsx';
import { useOrdersDispatch, useOrdersValues } from '../context/context.js';
const Main = () => {
const { orders } = useOrdersValues();
const { dispatch } = useOrdersDispatch();
if (!orders) {
return <div>Loading...</div>;
}
// delete order
const removeOrders = (id) => {
dispatch({type: "REMOVE_ORDER", id})
}
const arrowUp = (id,price) => {
dispatch({type: "INCREMENT", id});
}
const arrowDown = (id) => {
dispatch({type: "DECREMENT", id});
}
// render cards
const render_orders = (arr) => {
const list = arr.map((item) => {
return <Card key={item.id}
{...item}
removeOrder={removeOrders}
counterUp={arrowUp}
counterDown={arrowDown}/>
})
return list
}
const list_orders = render_orders(orders)
return (
<div>
<h1>YOUR BAG</h1>
{list_orders}
</div>
);
}
export default Main;
import "./card.scss";
import { FaAngleUp } from "react-icons/fa6"
import { FaAngleDown } from "react-icons/fa6"
const Card = ({id, img, price, title, amount, removeOrder, counterUp, counterDown}) => {
return (
<div>
<div className="content_order">
<div className="img_order">
<img src={img} alt="" />
</div>
<div>
<p className="title">{title}</p>
<p className="price">{price}</p>
<button onClick={() => removeOrder(id)}>remove</button>
</div>
</div>
<div className="counter_bag">
<button onClick={() => counterUp(id)}><FaAngleUp /></button>
<span>{amount}</span>
<button onClick={() => counterDown(id)}><FaAngleDown /></button>
</div>
</div>
)
}
export default Card;
The issue I'm experiencing is that the newPrice
variable appears to update continuously when I increment the item quantity. My goal is to add the fixed price to the total price with each increment of the item counter.
The INCREMENT
reducer case is also updating the order price, each time doubling it.
case "INCREMENT":
const { id } = action;
const updatedOrders = orders.map((order) => {
if (order.id === id) {
const newAmount = order.amount + 1;
// Next price is twice current order price
const newPrice = parseFloat(order.price) + parseFloat(order.price); // <-- 2x
return { ...order, amount: newAmount, price: newPrice };
} else {
return order;
}
});
return updatedOrders;
Computing a total value from some quantity and cost value is often considered derived state and it's a bit of a React anti-pattern to store derived state in state. If you must compute and store a total price value in state then I suggest leaving the unit price alone and add a new total
property to the state for the computed value.
Example:
case "INCREMENT":
const { id } = action;
const updatedOrders = orders.map((order) => {
if (order.id === id) {
const newAmount = order.amount + 1;
return {
...order,
amount: newAmount,
total: newAmount * Number(order.price), // <-- update total, not price
};
} else {
return order;
}
});
return updatedOrders;
const Card = ({
id,
img,
total, // <-- not price
title,
amount,
removeOrder,
counterUp,
counterDown
}) => {
return (
<div>
<div className="content_order">
<div className="img_order">
<img src={img} alt="" />
</div>
<div>
<p className="title">{title}</p>
<p className="total">{total}</p> // <-- total, not unit price
<button onClick={() => removeOrder(id)}>remove</button>
</div>
</div>
<div className="counter_bag">
<button onClick={() => counterUp(id)}><FaAngleUp /></button>
<span>{amount}</span>
<button onClick={() => counterDown(id)}><FaAngleDown /></button>
</div>
</div>
);
};
However, as I stated, computing a total and storing it in the state is a React anti-pattern. Just update the item quantity and compute the derived item total when rendering.
Example:
case "INCREMENT":
return orders.map((order) => order.id === action.id
? { ...order, amount: order.amount + 1 }
: order
);
const Card = ({
id,
img,
price,
title,
amount,
removeOrder,
counterUp,
counterDown
}) => {
const total = amount * Number(price); // <-- compute total
return (
<div>
<div className="content_order">
<div className="img_order">
<img src={img} alt="" />
</div>
<div>
<p className="title">{title}</p>
<p className="total">{total}</p> // <-- total
<button onClick={() => removeOrder(id)}>remove</button>
</div>
</div>
<div className="counter_bag">
<button onClick={() => counterUp(id)}><FaAngleUp /></button>
<span>{amount}</span>
<button onClick={() => counterDown(id)}><FaAngleDown /></button>
</div>
</div>
);
};