Why is this helper function returning a value for subtotal and tax but NAN for fees and ordertotal?
OrderSummary.jsx
const taxRate = 0.0925;
const OrderSummary = ({ addCSS, classes, merchantId, merchantName, step, setStep }) => {
const context = useContext(ProductContext);
let items = Array.isArray(context.cart) ? context.cart : [];
if (step === 4) {
items = Array.isArray(context.order) ? context.order : [];
}
const subtotal = items.reduce((acc, cur) => {
return acc + cur.total * (cur.company_id === merchantId) * 1;
}, 0);
let tax = subtotal * taxRate;
tax = parseFloat(tax.toFixed(2));
let fee = subtotal * ApplicationFee * 0.01;
fee = parseFloat(fee.toFixed(2));
const total = subtotal + tax + fee;
<LineItem>
<S2>Subtotal</S2>
<S2>{formatCurrency(subtotal)}</S2>
</LineItem>
<LineItem>
<S2>Tax</S2>
<S2>{formatCurrency(tax)}</S2>
</LineItem>
<LineItem>
<S2>Fee</S2>
<S2>{formatCurrency(fee)}</S2>
</LineItem>
<Total>
<H5>Order Total</H5>
<H5>{formatCurrency(total)}</H5>
</Total>
helperFunctions.jsx
export const formatCurrency = (num) => {
if (typeof num !== 'number') return num
return new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'}).format(num)
}
addTotals = () => {
let subTotal = 0;
this.state.cart.map((item) => (subTotal += item.total));
const tempFees = subTotal * ApplicationFee * 0.01;
const fees = parseFloat(tempFees.toFixed(2));
const total = subTotal + fees;
this.setState(() => {
return {
cartSubTotal: subTotal,
cartFees: fees,
cartTotal: total,
};
});
};
Image shows a Subtotal and Tax being calculated but NAN for Fee and Order Total
Believe it or not, but NaN (Not A Number) is a number:
typeof NaN !== 'number' // false
... so your first check just misses that case, and NaN (as value) gets nicely formatted to $NaN string by Intl.NumberFormat.
So you should consider checking how exactly fee is calculated in your case. This might fix your orderTotal, too (any calculation involving NaN results in NaN).
Another option is adding specific NaN check to your formatting function to prevent showing weird characters, like this:
if (Number.isNaN(num)) // ...
... but it's really hard to tell what should be displayed in this case.
Related
I'm trying to implement a decrement functionality for decimal values in js
On clicking a button the below should happen
1.22 -> 1.21
3.00 -> 2.99
1.2345 -> 1.2344
How could I do that, below is my code
var decimals = 1,
stringC = String(positionData.volume),
value = parseFloat(positionData.volume);
if (stringC.includes(".")) {
const splitted = stringC.split(".");
decimals = splitted[1];
}
const length = decimals?.length;
if (length > 0) {
decimals = "0.";
for (let i = 0; i < length; i++) {
if (i == length - 1) {
decimals += "1";
}
decimals += "0";
}
}
console.log(decimals);
decimals = parseFloat(decimals).toFixed(2);
setCloseValue((value) =>
(parseFloat(value) + decimals).toString()
);
Above is my code but it is appending the values as a string
EDIT: I just realized only decrementing was requested. Oh well - now this answer works for incrementing too... and also steps larger than ±1.
I would solve this as follows:
const input = document.getElementById('input')
function applyStep (step) {
const decimals = input.value.split('.')[1]?.length ?? 0
const stepSize = 10 ** -decimals
input.value = (Number(input.value) + stepSize * step).toFixed(decimals)
}
document.getElementById('increment').addEventListener('click', () => applyStep(1))
document.getElementById('decrement').addEventListener('click', () => applyStep(-1))
<input id="input" value="1.23" />
<button id="increment">Inc</button>
<button id="decrement">Dec</button>
What I do here is this: I first check how many decimals we have after the . (defaulting to 0 if there is no dot).
Then I calculate a step size of 10-decimals, e.g. with 3 decimals we get 10-3 which is 0.001.
Then I add the given step (+1 or -1) multiplied by step size to the numeric value and reformat it with the previous number of decimals before writing it back into the field.
Note that this won't work with numbers that go beyond the precision limits of JavaScripts default number type. If that is desired, a more complicated approach would be needed.
It could be done by handling integer and fractional parts separately as two BigInts, but it needs a lot of thought when it comes to carry and negative values. Here is a possible solution:
const input = document.getElementById('input')
function applyStep (step) {
if (!input.value.match(/^-?\d+(\.\d*)?$/)) throw new Error('Invalid format')
const [strIntegerPart, strFractionalPart = ''] = input.value.split('.')
let bnIntegerPart = BigInt(strIntegerPart)
let bnFractionalPart = BigInt(strFractionalPart)
const decimals = strFractionalPart.length
const maxFractionalPart = 10n ** BigInt(decimals)
let negative = strIntegerPart.startsWith('-') && (bnIntegerPart || bnFractionalPart)
if (negative) {
bnIntegerPart *= -1n
step *= -1n
}
bnFractionalPart += step
const offset = bnFractionalPart < 0n ? maxFractionalPart - 1n : 0n
const carry = (bnFractionalPart - offset) / maxFractionalPart
bnIntegerPart += carry
bnFractionalPart -= carry * maxFractionalPart
if (bnIntegerPart < 0n) {
if (bnFractionalPart) {
bnIntegerPart += 1n
bnFractionalPart = maxFractionalPart - bnFractionalPart
}
bnIntegerPart *= -1n
negative = !negative
}
let newValue = bnIntegerPart.toString()
if (decimals > 0) newValue += '.' + bnFractionalPart.toString().padStart(decimals, '0')
if (negative && (bnIntegerPart || bnFractionalPart)) newValue = '-' + newValue
input.value = newValue
}
document.getElementById('increment').addEventListener('click', () => applyStep(1n))
document.getElementById('decrement').addEventListener('click', () => applyStep(-1n))
<input id="input" value="1.23" />
<button id="increment">Inc</button>
<button id="decrement">Dec</button>
Both these solutions will work with steps other than 1 and -1 too.
const decrementVarable = (decimal)=>{
let decToString = decimal.toString().split('.')
if(decToString.length > 1){
let value = decToString[1]
let number = Number('1'+ '0'.repeat(value.length))
let numWithoutDecimal = ((decimal * number)-1)/number
return numWithoutDecimal
}
return (decimal-1);
}
use this code just pass your decimal
Floating points are usually messy. If you really need it to be 100% precise, use an integer variable, increment that by 1 in each iteration, test for when it reaches 100, and then, for the actual calculation, take your variable and divide it by 100 to obtain the decimal value you need.
How do I filter the data in collection in Firestore
If the first letter is "+" then I want to get filtered in Income section if the first letter is "-" then I want to get filtered in Expenditure Section
I tried this, but not working:
const getUsers = async()=>{
total_income_amount = await db.collection("users").get().then((querySnapshot) => {
const sum = querySnapshot.docs.filter((item) => item > 0).reduce((a, b) => a + b.data().amount, 0)
return sum
})
}
I am getting the output as 0
and I want the output with two decimal places
EDIT
Here's my code:
total_amount = db.collection("users").get().then((querySnapshot) => {
var total_sum = 0;
var income_sum = 0;
var exp_sum = 0;
querySnapshot.docs.forEach(doc => {
const amount = doc.data().amount;
amount >= 0.0? income_sum += amount : exp_sum -= amount;
total_sum += amount;
});
return {total: total_sum, income: income_sum, expense: exp_sum }
}).finally(result => console.log("Total: ", result));
You can loop through the list of values and append the values once rather than looping through it 5 times with a reduce & filter
total_amount = db.collection("users").get().then((querySnapshot) => {
var total_sum = 0;
var income_sum = 0;
var exp_sum = 0;
querySnapshot.docs.forEach(doc => {
const amount = doc.data().amount;
amount >= 0.0? income_sum += amount : exp_sum -= amount;
total_sum += amount;
});
return {total: total_sum, income: income_sum, expense: exp_sum }
})
.finally(result => console.log("TOTALS", result);
EDIT
It's not good to make major changes to your Question
but in general, you can truncate all number's to a decimal place using toFixed() on the final output
https://www.w3schools.com/jsref/jsref_tofixed.asp
UPDATE
Ensure your numbers are converted to real numbers from user inputs with Number(value) if the typeof returned is not a number, you can know if it is valid or not.
Once this has been done, ensure you return the correct calls as needed as scripts also only fire once.
when rendering values inside HTML, you want to use an embedded <span> tag with the appropriate id tag rather than editing it via javascript
I am in the freeCodeCamp Use the reduce Method to Analyze Data challenge.
I tried:
function getRating(watchList){
// Add your code below this line
var count = 0;
var sum = 0;
var averageRating = watchList.reduce(function (obj) {
if (obj.Director === "Christopher Nolan") {
count++;
sum = sum + parseFloat(obj.imdbRating);
}
return sum;
}, 0) / count;
// Add your code above this line
return averageRating;
}
The result is NaN, what I am doing wrong? I must use the arrow function, because standard function declaration produced unexpected results?
Thanks for help
I will give you hint.
const res = watchList.filter((d) => d.Director === 'James Cameron').map(x => x.averageRating));
Then another map to Numbers
res.map(Number)
Now use reduce to calculate average of numbers. Everything is explained here.
you can try:
const relevant = watchList.filter(movie => movie.Director === "Christopher Nolan");
const total = relevant.reduce((sum, movie) => sum + Number(movie.imdbRating), 0);
const average = total / relevant.length;
or combine the last 2 lines:
const relevant = watchList.filter(movie => movie.Director === "Christopher Nolan");
const average = relevant.reduce((sum, movie) => sum + Number(movie.imdbRating), 0) / relevant.length;
function getRating(watchList){
// Add your code below this line
var titles = watchList.filter(function(obj){
return obj.Director === "Christopher Nolan";
});
//message(titles);
var averageRating = titles
.reduce( function(sum,obj){
return sum = sum + parseFloat(obj.imdbRating);
}, 0)/titles.length;
//message(watchList.Director);
// Add your code above this line
return averageRating;
}
let averageRating = watchList
.filter(movie => movie.Director === "Christopher Nolan")
.map(movie => Number(movie.imdbRating))
.reduce((sum, movie, index, arr) => ((movie/arr.length)+ sum), 0);
in this component I have a button an i have a function for the onClick event, sayHello to test it i just put a console.log to see if it fires when it is clicked, but it only fires when in render the function is called this.sayHello().
import React, { Component } from "react";
import AsteroidDetails from "./AsteroidDetails";
export default class Asteroids extends Component {
state = {
percentageChem: [],
remainingMass: [],
asteroidYield: [],
asteroid: this.props.item,
};
sayHello = () => {
console.log("clicked");
};
/*** calculations
*
remaining m = (mass- all percentages of day )
m is the remaining mass of the asteroid:
remaing mpct = (mass - all percentages of day / mass x 100
mpct is the remaining percentage mass of the
asteroid
*/
/** yield calculation
*
* for mpct >= 0,02
* yield = (0,04 + ln(mpct) / 100) * m
*
* for mpct < 0,02
* yield = 0
*/
// function to calculate percentage of chemicals
percentage = (num, per, t) => {
return ((num / 100) * per) / t;
};
// function to get the sum of all the percentage for one asteroid
sumPercentage = (percentagearr) => {
return percentagearr.reduce(function (a, b) {
return a + b;
}, 0);
};
//function for the remaining mass calculation
remainingMass = (mass, sum) => {
return mass - sum;
};
//function for the remaining mpct calculation
remainingMpct = (mass, sum) => {
return ((mass - sum) / mass) * 100;
};
//write yield function
calcYield = (mass, mptc) => {
if (mptc >= 0.02) {
return (0.04 + Math.log(mptc) / 100) * mass;
} else {
return 0;
}
};
componentDidMount() {
console.log(this.props, "props inside Component Did mount"); // props of each asteroid
const mass = this.props.item.mass;
let t = 1;
t = t + Math.round(Math.log10(mass));
let percentageChem = []; // an empty array for the percentages of chemicals
Object.entries(this.props.item.chemicals).forEach(([key, value]) => {
let chem = this.percentage(mass, value, t).toFixed(2); // calculate percentage of chemicals
percentageChem.push(chem); // push the calulated peprcentage inside percentageChemicals array
});
//setting the state for the percentage of chemicals
this.setState({
percentageChem: percentageChem,
});
console.log(percentageChem, "percentage Chemicals");
// changing the string array of percentage chemicals to number array
let numberPercentArray = percentageChem.map((el) => parseFloat(el));
// calculate the sum of the percentage chemicals array
let sumOfPercentage = this.sumPercentage(numberPercentArray);
//total sun if percentage array
const totalSum = sumOfPercentage.toFixed(2);
console.log(totalSum, "sum");
// calculation remaining mass using the total sum and mass.
const remMass = this.remainingMass(mass, totalSum);
this.setState({ remainingMass: remMass });
console.log(remMass, "remaining mass");
// calculation of remainingmpct using total sum and mass.
const mpct = this.remainingMpct(mass, totalSum).toFixed(2);
this.setState({ remainingMpct: mpct });
console.log(mpct, "remaining mpct");
//call yield function
const turnover = this.calcYield(mpct, mass);
this.setState({ asteroidYield: turnover });
console.log(turnover, "YIELD?");
}
render() {
return (
<div className="as">
<AsteroidDetails
chemPerDay={this.state.percentageChem}
asteroid={this.state.asteroid}
remainingMassPerday={this.state.remainingMass}
asteroidYieldPerDay={this.state.asteroidYield}
/>
<button onClick={this.sayHello}>Click</button>
</div>
);
}
}
Thats because are invoking function on each render. You need to create closure and put your function in it.
Try this:
<button onClick={()=> {this.sayHello()}}>Click</button>
I have an object, this.items, that I am running a for in on, then using the find method to get all the products by id. I'm attempting to set a salesTax amount based on a conditional (if taxPercent > 0), but the loop is going through more than the expected amount of times and thus the taxPercent is larger than required.
total() {
let total = 0
let tax = []
let importDuty = .05
for (let productId in this.items) {
total += this.inventory.products.find(product => {
if (product.taxPercent > 0) {
tax.push(product.price * product.taxPercent)
}
return product.id == productId
}).price * this.items[productId]
}
tax = tax.reduce((a, b) => a + b, 0)
let importDutyToApply = total * importDuty
total = total + importDutyToApply + tax
let response = {
'total': total,
'tax': tax
}
return response
}
I'm wondering how I can set the total tax to apply for any items that do not have the taxPercent set to 0. So, if I had three items and one of them required a 10 percent tax on $100, the total should be $10. Likewise if I had many items with some requiring the 10 percent tax and others not, it would only add up for the ones that require it. Any thoughts?
The function you pass to find gets called a load of times. You just want to add tax for the product it found, not for everything it looked at on the way.
const product = this.inventory.products.find( product => product.id == productId );
if (product.taxPercent > 0) {
tax.push(product.price * product.taxPercent)
}
total += product.price * this.items[productId]