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.
Related
I'm trying to add a list inside a state using the set method, but my state remains empty
App.js
// Starts the screts word game
const startGame = () => {
// pick word and pick category
const { word, category } = pickWordAndCategory();
// create array of letters
let wordLetters = word.split("");
wordLetters = wordLetters.map((l) => l.toLowerCase());
// Fill states
setPickedCategory(category);
setPickedWord(word);
setLettersList(wordLetters);
console.log('wordLetters', wordLetters);
console.log('lettersList', lettersList);
setGameState(stages[1].name);
};
const pickWordAndCategory = () => {
// pick a random category
const categories = Object.keys(words);
const category = categories[Math.floor(Math.random() * Object.keys(categories).length)];
console.log('category', category);
// pick a random word
const word = words[category][Math.floor(Math.random() * words[category].length)]
console.log(word);
return { word, category };
}
Here is the browser log
When looking at the log we find our state empty
When you use setLettersList(...), you are not actually changing lettersList variable itself. Notice how when you declare it, const [lettersList, setLettersList] = useState([]); letterList is actually constant, so that variable can't even be changed. Instead, it tells React to rerun the function component, this time giving lettersList the new value. Therefore, for the updated value, it will only come once const [lettersList, setLettersList] = useState([]); is rerun again from the entire function being rerun again.
If you want to monitor changes to lettersList, you can do:
useEffect(() => {
console.log(lettersList);
}, [lettersList]);
This will print lettersList every time it is updated.
States updates are asynchronous, so when you set a new state value and console.log() right after it, it's going to show the previous value, because the state hasn't been updated yet.
That's why your lettersList value show the old value of the state in the console (empty array).
First time I use Dinerojs. I use it inside my shoppingcart which is pure js file.
I'm including part of the code that I think is relevant here but if you need me to post more code, I will be append my question.
My shopping cart code is simple - it's a svelte store:
let cartStore = writable({
items: [],
totalPrice: 0,
totalItems : 0
})
and I add items to the shopping cart using the following code :
cartStore.update(cartStore => {
cartStore.items.push({
product: product,
totalPrice: product.price,
quantity: 1
})
To compute the item totalPrice I'm trying to multiply item.totalPrice by quantity:
updatedCartStore.items.forEach(item => {
item.totalPrice = item.product.price * item.quantity;
});
Now this code works before I added Dinerojs to my package.json and attempted to use DineroJS. When I tried to use the Dinero object, I ran into issues which I will explain shortly.
I changed my totalPrice to this:
let cartStore = writable({
items: [],
totalPrice: Dinero({amount : 0, currency : "USD"}), // >>>>changed this to include Dinero
totalItems : 0
})
and when I tried to multiply item.product.price by item.quantity...I got an error that I need to provide an integer, console.log is showing totalPrice as NAN. this is my code:
updatedCartStore.items.forEach(item => {
item.totalPrice = Dinero({ amount : item.product.price}).multiply(item.quantity);
});
I also tried the following which gave me the error "multiply is not a function" :
updatedCartStore.items.forEach(item => {
item.totalPrice = item.product.price.multiply(item.quantity);
});
So, my question is how to calculate item.totalPrice using Dinero().multiply() ? I need to multiply item.product.price by item.quantity but using Dinero.JS. What is the right way to accomplish this?
Update 1:
Mybe I added Dinero to my front wrong:
My front is this:
<ProductCard name ="dinerojs" description="to test using dinerojs" price ={Dinero({amount :40 , currency:"USD" }).toFormat()} options="false" />
Mybe toFormat() causes the number to convert to something else?
Dinero requires the "amount" to be an integer value. It tracks monetary trasactions in "cents", not "dollars" (as they call, "Minor Currency", since it supports multiple currencies)
Depending on the currency, you may need to act differently, but the default number of decimal places is 2, so as long as you don't change that, you just need to convert item.product.price to an integer:
updatedCartStore.items.forEach(item => {
item.totalPrice = Dinero({ amount : item.product.price * 100}).multiply(item.quantity);
});
Just keep in mind, if you switch to Yen or Peruvian Pesos or something, you may need to adjust things.
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'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.