I'm having a product with my calculations in react/redux.
In my redux state for single product price is 400 and for single product quantity is 1,
So, I created a selection input from 1- 10 for updating the single product quantity state in my redux store. Now whenever user selects a number ranging from 1 to 10, the number will automatically update the single product quantity. Fine that works well!
Now the problem is, I can't update the product price after the quantity has been updated 🙃
For example, if a user selects 6 as quantity in the first run it works well but if the user decided to change the quantity from 6 to 2 or maybe 5, the product price will automatically update the product price by 6 ×2 ×5.
Is there anyway I can do it so whenever a user selects an option it gets multiply by the initial product price state instead of using the newly created state. Please !
STATES IN CART PRODUCTS COMPONENT
const quantityNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const [qtyValue, setQtyValue] = useState();
const [totalProductPrice, setTotalproductPrice] = useState(data.newPrice);
const selectQuantityNumber = quantityNumbers.map((num) => (
<Option value={num}>{num}</Option>
));
SELECTION COMPONENT
<Select
labelInValue
defaultValue={{ value: data.units }}
style={{
width: "50px",
backgroundColor: "transparent",
border: "0",
}}
onChange={selectUnitHandle}
bordered={false}
>
{selectQuantityNumber}
</Select>
SELECT FUNCTIONS
const selectUnitHandle = (e) => {
setQtyValue(e.value);
};
//updateQty function is Set for dispatching redux action for choosing qty and updating single product price
function updadeQty(item, qty, price) {
qty = qtyValue;
price = totalProductPrice;
dispatch(chooseQty(data, qtyValue));
setTotalproductPrice(data.newPrice * qtyValue);
dispatch(UpdateSinglePrice(data, qty, price));
}
MY REDUCER FOR UPDATING PRODUCT PRICE
case "UPDATE_SINGLE_PRICE": {
let newItemPrice;
let newItemQty;
let updatedItem = state.cartItems.find(
(prd) => action.item.id === prd.id
);
if (updatedItem) {
newItemPrice = action.price;
newItemQty = action.unit;
updatedItem.newPrice = newItemPrice * newItemQty;
} else return { ...state };
}
Avoid mutating the default price you have in the store i.e do away with the part of code that changes the price value in the store, instead, calculate the price value of at the point of rendering as:
item.price * item.qty
All you need to update in the store is the item quantity, let them item price be as is. That should fix(and simplify) the total price claculation.
Related
I am trying to make an addition of a grocery quantity in my react native app. The groceries are coming from firebase firestore with pre-defined prices, I want that when I increase the quantity, the total count of the cart_items should be correctly calculated. I am approaching this by adding it directly from the server.
The issue is, I need to be able to get only the initial price of a grocery, so I can add and subtract at will, instead, I am getting the updated price when I add the quantity, and that updated price is being added to the current price when I need to increase the quantity again. I hope you get what I mean.
const increment = async (id) => {
const itemRef = doc(db, "cartItems", id);
await getDoc(itemRef).then(async (snapshot) => {
// This Line of code is supposed to capture the initial value of the price
let price = snapshot.data().data.price;
console.log(price);
// This Line of code is supposed to capture the initial value of the price
await updateDoc(itemRef, {
quantity: snapshot.data().quantity + 1,
data: {
...snapshot.data().data,
price: snapshot.data().data.price + price,
// I am supposed to use that initial value for this calculation
},
});
});
};
And here's for decreasing the quantity
const decrement = async (id) => {
const itemRef = doc(db, "cartItems", id);
await getDoc(itemRef).then(async (snapshot) => {
// This Line of code is supposed to capture the initial value of the price
let price = snapshot.data().data.price;
console.log(price);
// This Line of code is supposed to capture the initial value of the price
await updateDoc(itemRef, {
quantity:
snapshot.data().quantity === 1 ? 1 : snapshot.data().quantity - 1,
data: {
...snapshot.data().data,
price:
snapshot.data().data.price === price
? price
: snapshot.data().data.price - price,
// I am supposed to use that initial value for this calculation
},
});
});
};
So I just need to know if there's a way I can get only the initial value of the price and not the updated value. Please let me know if I need to clarify anything about the question. It's a really pressing issue for me right now.
I got my answer guys.
I just had to add an initial value that remains constant and doesn't change to my database. That's what I use to make the necessary calculations on my app.
I want to make a call the database (firestore) to return the data of the products in real time that the user has added them to the cart then this data will be used in a code to get the total price of the cart items so I have tried the following approach:
This useEfect will get make the database call and will set the items state to to an array of objects that contains the id and quantity of each cart item on real time
const [items, setItems] = useState([]);
const [subTotal, setSubTotal] = useState([]);
const [Total, setTotal] = useState(0);
const oneSubTotal = [];
useEffect(() => {
db.collection("users").doc("4sfrRMB5ROMxXDvmVdwL").collection("basket").onSnapshot((docs) => {
let array = []
docs.forEach(doc =>{
array.push(doc.data())
console.log(array)
setItems(array)
})
});
}, [])
This code should loop through the items array to add an the element item.price to each object after getting the price of each item by another call to the database then it push to the Subtotal array the total price of each item by pushing the quantity multiplied by the price
useEffect(() => {
items && items.forEach((item) => {
// console.log(item)
const id = item.id
db.collection("products").doc(id).get().then((e)=>{
item.price = (e.data().price)
oneSubTotal.push(item.price * item.quantity)
setSubTotal(oneSubTotal)
})
})
then this code will loop through the subtotal array to get the sum of the price of the items
let sum = 0;
for (let num of subTotal){
sum = sum + num
}
useEffect(() => {
setTotal(sum)
}, [sum, items])
but the issue is the value of Total when the page renders first time always will be the total price of the first item which is represented with the first object in the items array and when I modify the quantity of any item (without refreshing the page) the Total value shows the correct amount for few seconds then the value of it returns to show the first items total price only
In your onSnapshot handler, you are calling console.log(array) and setItems(array) prematurely. This potentially causes your app to be rendered multiple times. You should make sure to be calling these lines outside of the forEach loop.
.onSnapshot((docs) => {
let array = []
docs.forEach(doc => {
array.push(doc.data())
});
console.log(array)
setItems(array)
});
But even so, it would be more efficient to fetch the item prices before calling setItems. Plus, instead of calling out to the database one-by-one using forEach, you should bundle the requests into batches like shown in this answer which is available as a utility function, fetchDocumentsWithId().
.onSnapshot((docs) => {
const cartItems = [], itemPriceObject = {};
// cartItems will be ({ id: string, quantity: number, price: number | null, lineTotal: number })[]
// itemPriceObject will be a map of IDs to their price (a Record<string, number | null>) (used to deduplicate IDs & store prices)
docs.forEach(doc => {
const cartItem = doc.data()
cartItems.push(cartItem)
itemPriceObject[cartItem.id] = null
});
// idsArray is a deduplicated list of IDs
const idsArray = Object.keys(itemPriceObject);
fetchDocumentsWithId(
db.collection("products"),
idsArray,
(itemDoc) => {
itemPriceObject[itemDoc.id] = itemDoc.get("price") // more efficient than itemDoc.data().price
}
)
.then(() => {
// if here, all prices (that are available) have been retrieved
// MAY BE NULL! Consider these items to be "not available"
const totalSum = 0
// put prices in their items, calculate line cost and add to total
cartItems.forEach(item => {
item.price = itemPriceObject[item.id]
item.lineTotal = item.price === null ? 0 : item.price * item.quantity
totalSum += item.lineTotal
}
// set items & total sum
setItems(cartItems)
setTotal(totalSum)
})
.catch((error) => {
// failed to retrieve some documents from the database
// TODO: update UI
});
});
Note: For clarity, subTotal (meaning: the sum of some, but not all values) was renamed to lineTotal (meaning: the cost of items in this entry/line, the cost x quantity)
So I am trying to add a cart feauture to my React-redux site and I got stuck on a very weird occurance. So this is what I get from the payload of the action for example:
{
info: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
id: 1,
price: 109.95,
image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
count: 5,
totalPrice: 549.75
}
So what Im trying to do is, when an item with the same id as this one is trying to be added, to not add it, but to increase the count of the item with the same id that already exists in the cart:
const index = state.currentCart.findIndex((x) => x.id === id);
return {
...state,
currentCart: [
...state.currentCart,
state.currentCart[index].count += 1,
(state.currentCart[index].totalPrice =
state.currentCart[index].price * state.currentCart[index].count),
],
};
The count itself is increased, but there is something really strange happening at the same time.
The total price of the product and its count are also added as elements of the currentCart array, when the only thing that should happen is to update the count of the cart item with the id from the payload,
this is what happens to the currentCart array when this action is fired:
currentCart: [
{
info: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
id: 1,
price: 109.95,
image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
count: 6,
totalPrice: 659.7
},
2,
219.9,
3,
329.85,
4,
439.8,
5,
549.75,
6,
659.7
]
}
I am sure I am not mutating the state right, thank you in advance!
No, they are not coming from nowhere, you are actively adding the values to the array.
You seem to be a bit confused about how to properly handle states. You either choose an immutable approach (which I really recommend if you are using react) or you choose to mutate your references.
In javascript, when you do an assignment, that assignment also returns the value that is being assigned, so for example here:
let x = 1
let b = x+=1
// b is now 2 and x is 2
let c = b += 2
// b is now 4 and c is also 4
That is exactly what is happening on your array assignment. You are first spreading the old version of the array on the new one (making a copy) and then you mutate the reference to the current car at the same time (and this is the key part) that you are saving the return value of those assignments in the array itself.
Take a look at the values on the array, they are the results of your operations:
count (1) += 1 // 2
price (109.95) * count (2) = 219.9,
count (2) += 1 // 3
price (109.95) * count (3) = 329.85
... etc
So what you have on your array is an historic of the count and total price values.
This is a breakdown of what is happening in your code:
// Will allways be at index 0, because you add it as first element
// and then you keep copying the array below
const index = state.currentCart.findIndex((x) => x.id === id);
return {
...state,
currentCart: [
// Here you are copying the old array into the new one,
// keeping the current car at the first position
...state.currentCart,
// Here you are updating the values of the object at index 0
// and at the same time you are adding those values at
// the end of the array
state.currentCart[index].count += 1,
(state.currentCart[index].totalPrice =
state.currentCart[index].price * state.currentCart[index].count),
],
};
What you want to do is to build a new currentCart each time and. Also you want to use an object for currentCart, not an array. If you want to keep a list of items in the cart, I suggest you tu create a nested property on the cart called items, and make that be an array.
Your code example is not showing us where are you getting the action from, but I will provide you an example assuming you just have it and that the new item to add to the cart comes in the payload.
const currentCart = state.currentCart;
const newItem = action.payload
return {
...state,
currentCart: {
...currentCart,
count: currentCart.count + 1
totalPrice: (newItem.price * newItem.count) + currentCart.totalPrice,
items: [...currentCart.items, newItem]
},
};
I am not sure but this is happening
totalPrice = item's price * no of times the item is added
Other items' price is not getting included. Try this -
state.currentCart[index].totalPrice += state.currentCart[index].price * state.currentCart[index].count
(just '+=' instead of '=')
I have a dynamic form in Ant Design where I can:
set the price and the quantity in some inputs.
create new rows
I was able to access all the values from the rows after submiting with this piece of code:
const onFinish = values => {
console.log("Received values of form:", values);
};
But I would like to access each value from the inputs, get the total (price * quantity) and set it the total input.
I saw a solution in basic React from this question Calculating quantity and price total in react. That answer shows that you need use state and use the onChange event and work from there, but I don't really know how to translate this answer because I don't understand how to access the state from each row in Ant Design.
You can see my code right here: https://codesandbox.io/s/stupefied-hawking-pcr7k?file=/index.js:260-348
Any help would be appreciated
You can calculate derived values by taking advantage of the onValuesChange event handler on the Form component, and the form instance methods provided by the Form.useForm hook.
Please see sandbox example:
https://codesandbox.io/s/antdesign-calculating-a-derived-value-using-dynamic-form-hgyzh?file=/index.js:229-1020
const ItemForm = () => {
const [form] = Form.useForm()
const onFinish = values => {
console.log('Received values of form:', values)
}
const handleTotal = (_, values) => {
const rowsCopy = [...values.rows]
values.rows.forEach((fieldGroup, index) => {
if (fieldGroup && fieldGroup.quantity && fieldGroup.price) {
fieldGroup.total = fieldGroup.quantity * fieldGroup.price
rowsCopy.splice(index, 1, fieldGroup)
console.log('fieldGroup', fieldGroup)
console.log('rowsCopy', rowsCopy)
form.setFieldsValue({ rows: rowsCopy })
}
})
}
return (
<Form
name="dynamic_form_nest_item"
form={form}
onFinish={onFinish}
onValuesChange={handleTotal}
autoComplete="off"
size="small"
>
I am trying to create a cart with React js and Redux and I have one problem.
Tried many ways but I keep failing that when I add multiple items (food/drink) to the list then everything seems to be working, but then when I want for example add additional drink to the existing choice my list gets overwritten. Here is the code I have it now:
const addItemToCart = item => {
if (cartItems.length) {
cartItems.forEach(itemToCheck => {
if (item.name === itemToCheck.name) {
addCountToItem({ ...itemToCheck, count: itemToCheck.count + 1 });
} else if (item.name !== itemToCheck.name) {
addToCart({ ...item, count: 1 });
}
});
} else if (cartItems.length === 0) {
addToCart({ ...item, count: 1 });
}
};
Idea is that I can have multiple items on the list and unlimited number of same items within the list. So basically, I should be able to have 5 pizzas of the same type, 3 beers of different type etc.
I guess like any other cart. Thanks in advance.
update:
Here the code for addCountToItem. I deleted it but it was going something in this direction
state.cartItems[findIndex(...)] = data.cartItem
a basic way to solve your problem is
`let index=cartItem.findIndex(temp=>temp.name===item.name);
if(index>-1){
cartItem[index].count++;
}
else{
cartItem.append({...item, count: 1 })
}`
try not to mutate cartItem object
We need too see to all the related code to successfully answer.
Here I give sample example, updatedCartItems keeps the updated cart, you can do whatever you want. In general, this type of manipulation must be in the cart reducer, but you didn't post the the reducer code.
const addItemToCart = item => {
let updatedCartItems = [...cartItems];
updatedItemIndex = updatedCartItems.findIndex(
item => item.name === itemToCheck.name // better to check with some kind of id if exists
);
if (updatedItemIndex < 0) {
updatedCartItems.push({ ...item, count: 1 });
} else {
const updatedItem = {
...updatedCartItems[updatedItemIndex]
};
updatedItem.count++;
updatedCartItems[updatedItemIndex] = updatedItem;
}
//updatedCartItems => the new cart
};
for shopping card, we need to have cartItems property as array in our state, and every time we click on the addToCart button, we will push that item to that array and then we render that array in the cartDropdown component or the checkout page.
since you are able to add single item to the cart, it means that you have correct set up for redux. in order to add same item to the cart more than once, we just need to write a simple utility function.
here is the utility function:
export const addItemToCart = (cartItems, cartItemToAdd) => {
//find(condition) finds the first item in the array based on the condition.
const existingCartItem = cartItems.find(item => item.id === cartItemToAdd.id);
if (existingCartItem) {
//in order for change detection to trigger we have to rerender
//otherwise our quantity property will not be updated
//map will return a new array
//we need to return new versions of our state so that our component know to re render
//here we update the quantity property
return cartItems.map(item =>
item.id === cartItemToAdd.id
? { ...cartItemToAdd, quantity: item.quantity + 1 }
: item
);
}
//when you first time add a new item, sine exixtingCartItem will be falsy, it will pass the first if block and will come here
//quantity property gets attached the first time around since this if block wont run when it is a new item.
//in the beginning cartItems array is empty. every time you add a new item to this array, it will add "quantity:1" to this item object.
return [...cartItems, { ...cartItemToAdd, quantity: 1 }];
};
here is the action to add item to the cart
export const CartActionTypes = {
ADD_ITEM: "ADD_ITEM",
};
export const addItem = item => ({
type: CartActionTypes.ADD_ITEM,
payload: item
});
since you are able to add single item to the cart, it means that you have correct set up for redux. you need to dispatch this to the reducer in the component that you render addToCart button. here is the cart reducer where the case is CartActionTypes.ADD_ITEM.
import { addItemToCart } from "./cart.utils";
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: addItemToCart(state.cartItems, action.payload)
};