When the HandlePayNow function is called, it executes all of its intended functionality but then crashes the system. It sends the order data to the API, the API logs it and returns a success message, the function gets this success message, it displays it’s "Please Present Payment" alert with the correct numbers (matching the ones submitted when the button was clicked and those present in the logs), and then it crashes providing the error message:
Cannot read properties of undefined (reading 'toFixed')
TypeError: Cannot read properties of undefined (reading 'toFixed')
at http://localhost:3000/static/js/bundle.js:783:58
at Array.map (<anonymous>)
at App (http://localhost:3000/static.js.bundle.js:768:32)
at renderWithHooks (http://localhost:3000/static/js/bundle.js:22965:22)
at updateFunctionComponent (http://localhost:3000/static/js/buindle.js:27559:20)
at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:12557:18)
at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:12601:20)
at invokeGuardedcallback (http://localhost:3000/static/js/bundle.js:12658:35)
at beginWork$1 (http://localhost:3000/static/js/bundle.js:32532:11)
This was not occuring before I modified the CalculateRunningTotal function. I’m about 99% sure (as always is the case when I come to get the assistance of the amazing StackOverflow community) that this is simply a case of stupidity where I’m blatantly overlooking something very obvious. My gut instinct tells me it’s related to the way the subtotal state is being reset in the async, but I’m, frankly, terrible at JS. It’s not my bag. If someone could explain my silliness, that would be greatly appreciated.
All related functions:
const logToConsole = (message) => {
setConsoleLogs((prevLogs) => [...prevLogs, message]);
};
useEffect(() => {
const customConsoleLog = console.log;
console.log = (message) => logToConsole(message);
return () => {
console.log = customConsoleLog;
};
}, []);
const calculateItemTotal = (item) => {
let sizePrice = getSizePrice(item.size);
let flavorPrice = item.flavors.length * 0.89;
return sizePrice + flavorPrice + item.toppingsPrice;
};
const calculateRunningTotal = () => {
let total = 0;
for (const itemTotal of runningTotals) {
total += itemTotal;
}
if (selectedDiscount) {
const discount = discounts.find((discount) => discount.name === selectedDiscount);
if (discount) {
const discountMultiplier = 1 - discount.discount;
total *= discountMultiplier;
}
}
// Check if total is NaN before applying toFixed
return isNaN(total) ? 0 : total.toFixed(2);
};
const handleAddToOrder = () => {
if (!selectedSize) {
alert("Select a Size!");
return;
}
const item = {
size: selectedSize,
flavors: selectedFlavors,
toppingsPrice: toppingsPrice,
};
setOrderItems((prevItems) => [...prevItems, item]);
setSubtotal((prevSubtotal) => prevSubtotal + calculateItemTotal(item));
setRunningTotals((prevTotals) => [...prevTotals, calculateItemTotal(item)]);
setSelectedSize(null);
setSelectedFlavors([]);
setToppingsPrice(0);
setSyrupToppings((prevToppings) =>
prevToppings.map((topping) => ({
...topping,
isActive: false,
}))
);
};
const getSizePrice = (size) => {
switch (size) {
case 'Small':
return 2.49;
case 'Medium':
return 3.29;
case 'Large':
return 4.19;
default:
return 0;
}
};
const handleAddTopping = (price) => {
setToppingsPrice((prevPrice) => prevPrice + price);
};
const calculateCurrentItem = () => {
let sizePrice = selectedSize ? getSizePrice(selectedSize) : 0;
let flavorPrice = selectedFlavors.length * 0.89;
let total = sizePrice + flavorPrice + toppingsPrice;
if (selectedDiscount) {
const discount = discounts.find((discount) => discount.name === selectedDiscount);
if (discount) {
const discountMultiplier = 1 - discount.discount;
total *= discountMultiplier;
}
}
return total.toFixed(2);
};
const handleRemoveOrder = (index) => {
const removedItem = orderItems[index];
const removedItemTotal = calculateItemTotal(removedItem);
setOrderItems((prevItems) => prevItems.filter((_, i) => i !== index));
setRunningTotals((prevTotals) => prevTotals.filter((_, i) => i !== index));
setSubtotal((prevSubtotal) => {
const newSubtotal = prevSubtotal - removedItemTotal;
return newSubtotal < 0 ? 0 : newSubtotal;
});
};
const handlePayNow = async () => {
if (orderItems.length === 0) {
alert("No items in the order!"); // Make user select more than 0 items
return;
}
let name = prompt("Enter customer name:"); // Prompt for customer name
while (!name) {
name = prompt("Enter customer name:"); // Re-prompt for customer name
}
setCustomerName(name); // Set the customer name
// Reset the subtotal
setSubtotal(0);
let originalTotal = subtotal;
let discountAmount = 0;
let discountType = "None"; // Default value for discount type
if (selectedDiscount) {
const discount = discounts.find((discount) => discount.name === selectedDiscount);
if (discount) {
const discountMultiplier = 1 - discount.discount;
discountAmount = originalTotal - (originalTotal * discountMultiplier);
originalTotal = originalTotal * discountMultiplier;
discountType = discount.name; // Store the discount type
}
}
const logs = [];
logs.push('');
logs.push('');
logs.push(`Size: ${selectedSize ? selectedSize.charAt(0).toUpperCase() + selectedSize.slice(1) : null}`);
const flavorsText = selectedFlavors.length > 0 ? selectedFlavors.join(", ") : "None";
logs.push(`Flavors: ${flavorsText}`);
const nonCustomAddIns = syrupToppings.filter((topping) => topping.isActive && !topping.custom);
const selectedAddIns = nonCustomAddIns.map((topping) => topping.name);
if (selectedAddIns.length > 0) {
logs.push(`Add-ins: ${selectedAddIns.join(", ")}`);
} else {
logs.push("Add-ins: None");
}
const tax = (originalTotal * 0.056).toFixed(2);
const totalWithTax = (parseFloat(originalTotal) + parseFloat(tax)).toFixed(2);
logs.push('');
logs.push(`Subtotal: $${originalTotal.toFixed(2)}`);
logs.push(`Tax: $${tax}`);
logs.push(`Total: $${totalWithTax}`);
logs.push('');
setConsoleLogs(logs); // Update the console logs with the new order information
const updatedToppings = syrupToppings.map((topping) => {
const updatedTopping = { ...topping };
if (updatedTopping.isActive) {
updatedTopping.soldCount += 1;
}
updatedTopping.isActive = false;
return updatedTopping;
});
setSyrupToppings(updatedToppings);
setSelectedSize(null); // Reset selectedSize to null
setSelectedFlavors([]); // Reset selectedFlavors to an empty array
setToppingsPrice(0); // Reset toppingsPrice to 0
setSelectedDiscount(null); // Reset selectedDiscount to null
setRunningTotals([]); // Reset the running total
const soldLogs = syrupToppings
.filter((topping) => topping.isActive && !topping.custom)
.map((topping) => {
return {
name: topping.name,
price: topping.price,
isActive: topping.isActive,
soldCount: topping.soldCount + 1
};
});
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(soldLogs)
};
try {
const response = await fetch('http://localhost:5000/inventory', requestOptions);
if (response.ok) {
// Sold logs successfully updated in the inventory
console.log('Sold logs updated in the inventory.');
} else {
console.error('Failed to update sold logs in the inventory.');
}
} catch (error) {
console.error('Error occurred while updating sold logs in the inventory:', error);
}
// Send the relevant information to the simulated payment API
const paymentData = {
subtotal: originalTotal.toFixed(2),
tax: tax,
total: totalWithTax,
discount: selectedDiscount ? true : false,
discountAmount: discountAmount.toFixed(2),
discountType: discountType,
employee: loginNumberTracking,
};
try {
const paymentResponse = await fetch('http://localhost:5000/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(paymentData)
});
if (paymentResponse.ok) {
const paymentResult = await paymentResponse.text();
console.log('Payment API response:', paymentResult);
if (!selectedDiscount) {
alert(`Please Present PaymentnSubtotal: ${originalTotal.toFixed(2)}nTax: ${tax}nTotal: ${totalWithTax}`);
} else {
alert(`Please Present PaymentnSubtotal: $${originalTotal.toFixed(2)}nDiscount Amount: $${discountAmount.toFixed(2)}nDiscount Type: ${discountType}nTax: $${tax}nTotal (after discount and tax): $${(originalTotal + parseFloat(tax)).toFixed(2)}`);
}
} else {
console.error('Failed to send payment data to the API.');
}
} catch (error) {
console.error('Error occurred while sending payment data to the API:', error);
}
};
const handleSelectDiscount = (discount) => {
if (selectedDiscount === discount) {
setSelectedDiscount(null); // Deselect the currently selected discount
} else {
setSelectedDiscount(discount); // Select the clicked discount
}
};
StackOverflow is a last resort for me, generally speaking. If I’m here, I’ve been at it for several hours at least. I need someone who knows more than I do; my knowledge isn’t cutting it here.
2
Answers
There it was! The running total is calculated based on the added prices of items in the OrderItems array. Since, despite the RunningTotals array being cleared the OrderItems persisted within the array, this obviously caused a conflict. The OrderItems array needed to be reset BEFORE the RunningTotals array.
As the error message says:
The error is happening because you’re trying to call
toFixed()
method of anundefined
variable.From what you provided, it’s hard to tell correctly which part caused the issue but it could have happened from
subtotal
not being initialized and used.These lines:
React state setters are async so if your subtotal is
undefined
when you runsetSubtotal(0)
, it’s stillundefined
after running it, until after the next tick, which meansoriginalTotal
will also beundefined
and thus causing the error by callingoriginalTotal.toFixed(2)
.If this is not the case, there may be other statements that caused it.