I currently have code that when entering a screen with useFocusEffect, fetches an array of payments, and for each payment within that list there is another async function which is called that retrieves data about that payment including the value of it. I am trying to sum up the values of the payments in this list and set a state variable too it, but according to my understanding they are all happening at the same time and the state value is updated to just the value of the last payment that is read from the array. What would be the best way to make it so that my state updates to the sum of the values of the payments? I've tried various combinations of solutions with storing values in local variables and setting the state to the local variable in the end but nothing seems to work. Below is the code to help visualize what I am doing.
fetchPaymentsInChat(props.threadID).then((payments) => {
payments.forEach(pid => {
fetchPayment(pid).then(payment => {
setAmmountReceive(payment.value ammountReceive);
console.log(ammountReceive, payment.value);
if (payment.sender == username) {
setAmmountReceive(ammountReceive payment.value);
}
})
});
});
Any advice would be appreciated!
CodePudding user response:
Ultimately, it comes down to improper Promise chaining and a misunderstanding of how setState
callbacks work.
fetchPaymentsInChat(props.threadID)
.then((payments) => {
payments.forEach(pid => {
fetchPayment(pid) // <- floating Promises in loop
.then(payment => {
setAmmountReceive(payment.value ammountReceive); // <- sums early?
console.log(ammountReceive, payment.value); // improperly attempts to use new ammountReceive value
if (payment.sender == username) {
setAmmountReceive(ammountReceive payment.value); // <- attempts to sum again?
}
})
});
});
When using a setState
callback from a useState
call, it will set the value for the next render of your component - it doesn't update the value for the current render. There are resources online that explain how useState
works, but for the purpose of showing how it's not updated immediately, we can look at this pseudo-code:
const useState = (initValue) => {
useState._value = "_value" in useState ? useState._value : initValue;
return [useState._value, (v) => useState._value = v]
}
let [value, setValue] = useState(0);
console.log(value); // => 0
setValue(1);
console.log(value); // => 0 [but _value is now 1]
setValue(value 1);
console.log(value); // => 0 [but _value is still 1 (0 1)]
// --- start next loop --- (imagine we've gone back to useState(0) line)
[value, setValue] = useState(0);
console.log(value); // => 1
setValue(1);
console.log(value); // => 1 [but _value is still 1]
setValue(value 1);
console.log(value); // => 1 [but _value is now 2 (1 1)]
// --- start next loop --- (imagine we've gone back to useState(0) line again)
[value, setValue] = useState(0);
console.log(value); // => 2
setValue(1);
console.log(value); // => 2 [but _value is now back to 1]
setValue(value 1);
console.log(value); // => 2 [but _value is now 3 (2 1)]
// ...
As can be seen from the above lines, summing against the current value doesn't work as expected. While a setState
callback can be fed a function another callback to update the value that looks like (oldValue) => newValue
, we don't need to use it here.
This is because both issues can be solved by fixing up the Promise chains using Promise.all
combined with Array#map
and Array#reduce
.
fetchPaymentsInChat(props.threadID)
.then((paymentIDs) => {
return Promise.all(
paymentIDs.map(pid => {
return fetchPayment(pid)
.then(paymentDetails => paymentDetails.value)
})
);
})
.then((paymentValues) => {
const paymentSum = paymentValues.reduce((acc, val) => acc val, 0);
setAmmountReceive(paymentSum);
})
.catch(err => {
/* don't forget error handling */
})
If the intention is to only sum the payments where the sender is username
, instead use:
fetchPaymentsInChat(props.threadID)
.then((paymentIDs) => {
return Promise.all(
paymentIDs.map(pid => {
return fetchPayment(pid)
.then(paymentDetails => {
return paymentDetails.sender === username ? paymentDetails.value : 0
})
})
);
})
.then((paymentValues) => {
const paymentSum = paymentValues.reduce((acc, val) => acc val, 0);
setAmmountReceive(paymentSum);
})
.catch(err => {
/* don't forget error handling */
})