This question already has an answer here:
How to limit set the maximum decimal places of a number as 8 decimal places in React using javascript
(1 answer)
Closed 8 months ago.
I created a calculator in react, but when I do some division where the result will be a repeating decimal, this result is exceeding the calculator display.
Example: 1 / 3 = 0.333333333333
Could someone help me to make the result not pass the display?
I tried to use maxLength and toFixed methods, but neither worked
Here is my code:
export default function Calculator() {
const [num, setNum] = useState(0);
const [oldnum, setOldNum] = useState(0);
const [operator, setOperator] = useState();
const [waitingForNumber, setWaitingForNumber] = useState(false);
const [shouldClearNumber, setShouldClearNumber] = useState(false);
function inputNum(event) {
const input = event.target.value;
const number = (Number(num) + Number(input))
if (number > 999999999 && !waitingForNumber) {
return;
}
if (waitingForNumber || num === 0 || shouldClearNumber) {
setNum(input);
} else {
setNum(num + input);
}
setWaitingForNumber(false);
setShouldClearNumber(false);
}
function calcular() {
if (operator === "/") {
setNum(parseFloat(oldnum) / parseFloat(num));
}
if (operator === "X") {
setNum(parseFloat(oldnum) * parseFloat(num));
}
if (operator === "-") {
setNum(parseFloat(oldnum) - parseFloat(num));
}
if (operator === "+") {
setNum(parseFloat(oldnum) + parseFloat(num));
}
setShouldClearNumber(true);
console.log("calculou!!!!");
}
}
try using setState(Number(state).toFixed(1))
https://www.w3schools.com/jsref/jsref_tofixed.asp
The accepted answer isn't a good general solution, it doesn't limit the number of decimal points, it just cuts off characters that may or may not be after the decimal.
The correct answer is using toFixed, like Mel Carlo Iguis's answer. You could call toFixed everytime before setting state:
setNum(Number(newValue.toFixed(1))) // 1 for 1 decimal place, adjust as needed
Although that method loses information-- if you want to keep num high-precision for future calculations, you can instead just use toFixed during the actual render step. This is the pattern I'd recommend:
<div> {num.toFixed(1)} </div>
You could convert the number to a string, then use slice() to trim down the number to a specified amount of digits.
And convert it back to a number for your purposes.
For example if you wanted to keep 7 digits, you could do
num.toString();
num.slice(0, 6);
Number(num)
Related
I have a function that adds numbers to an array and then plays the sounds of the numbers and then removes the numbers after they are played at an interval called 'delay'. However, I have trouble figuring out where to reset the delay back to 0. That is - when all the numbers are played and removed from the array. The whole point of this is so that the first number being added will always be played immediately (at 0 second delay), while numbers being added afterwards will be played and removed at an interval like 4 seconds. I have spent a lot of time on solving this problem, but can anyone figure out where to reset the delay back to 0 so that when the array is empty, the first number being added will always be played immediately? Remember that if you keep adding numbers, the numbers will wait at an interval like 4 seconds to being played, like a sound queue.
const [arr, setArr] = React.useState([]); //Sound queue array
const interval = React.useRef(null);
const arr_length = React.useRef(null);
const [delay, setDelay] = useState(0);
function set_remove_arr_interval(num) {
setArr((currentNumbers) => [...currentNumbers, num]);
if (!interval.current) {
interval.current = setInterval(() => {
// Sets an ongoing interval for the sound queue
console.log("Array current position: " + arr_length.current);
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
setDelay(4000); //My delay duration at 4 seconds
}
if (arr_length.current === 0) {
// setDelay(0); // <-- I tried resetting the delay duration here to 0 but it resets to 0 every time a number is entered immediately and plays it, which is not what I want.
clearInterval(interval.current);
interval.current = null;
return;
}
arr_length.current = arr_length.current - 1;
setArr((currentNumbers) => [...currentNumbers.slice(1)]);
}, delay);
}
}
function add(num) {
arr_length.current = arr.length + 1;
set_remove_arr_interval(num);
}
Edit: I also tried initialising a new variable called isFirst, but it only works at alternating times of the interval:
const [isFirst, setIsFirst] = useState(true);
if (isFirst == false && arr_length.current == 0) {
setDelay(0);
setIsFirst(true);
}
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
if (isFirst == true) {
setDelay(4000);
setIsFirst(false);
}
}
I now solved the problem by simply resetting the delay back to 0 using the useState hook when the array's length is at 1. Does anybody have a better solution than this? Because I believe hooks are not encouraged to be used inside functions and I also found out that sometimes console.logging them does not log the new state when changed.
sound.setOnPlaybackStatusUpdate(async (status) => {
if (status.didJustFinish === true) {
const { sound } = await Audio.Sound.createAsync(data[0], {
shouldPlay: true,
});
setSound(sound);
await sound.playAsync();
if (arr.length == 1) {
setDelay(0);
}
}
});
My requirement is to split a to num in input so that it should not exceed total sum and for eg : if I enter 50 in any field other fields should have 25 each for 2 input values if there are 3 fields.
here is code sandbox for work on codesand box
Remove useEffect hook, the dependency for the hook is num and inside the callback of useEffect num is once again updated with setNum. This is a big no-no.
I suggest following changes to the handleChange callback
function handleChange(evt) {
const value = evt.target.value;
const inputElements = document
.getElementById("App")
.getElementsByTagName("input");
let numberOfInputElements = 1;
if (numberOfInputElements !== null) {
numberOfInputElements = inputElements.length - 1;
}
setNum((inputNum) => {
for (const key in inputNum) {
if (inputNum.hasOwnProperty(key)) {
inputNum[key] = value / numberOfInputElements;
}
}
return { ...inputNum, [evt.target.name]: value };
});
}
Link to codesandbox for complete code.
But I understand your requirement is slightly different from the problem statement.
So i'm building a calculator/estimator that is basically just a more complicated version of this margin calculator: https://www.omnicalculator.com/finance/margin
So here's one edge case that I'm trying to fix right now.
My costs are broken into 3 parts due to outsourced data- labor, material and laborAndMaterial. LaborAndMaterial is the sum of labor and material, but it can be the only known cost factor so that's why it's broken into 3 parts.
So here's the problem. Say we know that laborAndMaterial is set to 100 and labor and material are 0
cost: {
labor: 0,
material: 0,
laborAndMaterial: 100
}
Then the user enters 50 for labor. Because laborAndMaterial is 100 and labor is now 50 we can autofill material to 50.
But what's happening right now as the user is typing "50" it autofills material to 95 as the user types the 5 in 50. Then when they enter the "0" it sets the laborAndMaterial to 145 (50 + 95). But in that example I need to adjust how I autofill material to continue to update as the user enters more numbers (labor = 5 -> 50 -> 506) (material = 95, 50, -406). As of now I basically have my formula run like:
if(key === "cogs.labor") {
if(laborAndMaterial > 0) {
params["cogs.material"] = laborAndMaterial - value // value is what was entered
}
}
But I still need to allow for the other edge cause that as labor is entered and material is known it updates the laborAndMaterial value
cost {
labor: 50,
material: 50,
laborAndMaterial: 100
}
So if someone enters 100 for labor and we know material is 50 we can autofill laborAndMaterial to 150.
So I have something like:
if(material > 0) {
params["cogs.laborAndMaterial"] = material + value // value is what was entered
}
Any ideas how I can tweak my formula to decide the autofill and continue to update that paramater while still allowing for multiple edge cases?
The margin calculator from omnicalculator is a good example as they solved the issue, but I've been scratching my head over it for some time.
I think you basically need to differentiate between which cost centers are treated as input and which are treated as output. So when you start, each piece of data you're provided is input, and the data you use to autofill the rest of the form is output.
As the user types, any information they give is then treated as input data. Given that any two values can be used to calculate the third, you can only have two of the fields be treated as input at a time.
Here's a code sample to get an idea of what I mean:
// This is a queue to hold your two input cost centers
const inputFields = [];
// Determine the odd one out that we need to calculate
function getVariableCostCenter() {
if (!inputFields.includes('labor')) {
return 'labor';
}
if (!inputFields.includes('material')) {
return 'material';
}
return 'laborAndMaterial';
}
function calculateCostCenters(costCenters) {
const variableCostCenter = getVariableCostCenter();
if (variableCostCenter === 'labor') {
return {
...costCenters,
labor: costCenters.laborAndMaterial - costCenters.material,
};
}
if (variableCostCenter === 'material') {
return {
...costCenters,
material: costCenters.laborAndMaterial - costCenters.labor,
};
}
return {
...costCenters,
laborAndMaterial: costCenters.labor + costCenters.material,
};
}
function initializeCostCenters(costCenters) {
// First, we determine which field(s) are known up front
if (costCenters.labor > 0) {
inputFields.push('labor');
}
if (costCenters.material > 0) {
inputFields.push('material');
}
if (costCenters.laborAndMaterial > 0 && inputFields.length < 2) {
inputFields.push('laborAndMaterial');
}
// ...then do whatever you normally do to populate the form
}
function updateCostCenters(previousCostCenters) {
// Update the input fields so that the user's input
// is always treated as one of the two input fields
if (!inputFields.includes(key)) {
inputFields.shift();
inputFields.push(field);
}
const costCenters = calculateCostCenters({
...previousCostCenters,
[key]: value,
});
params['cogs.labor'] = costCenters.labor;
params['cogs.material'] = costCenters.material;
params['cogs.laborAndMaterial'] = costCenters.laborAndMaterial;
}
Pretty roughly it might look like below.
Note that I remembering last touched fields, which are became "fixed", because we can not recalculate values circularly.
Also, note that I use direct value update, while in some frameworks/libs it might generate change/input event, so you would want to set values silently.
setup = {
labor: {
value: 0
},
material: {
value: 0
},
laborAndMaterial: {
value: 100
}
};
// the number which we are treat as "fixed", may be changed later
let prevFixed = 'labor';
let fixed = 'labor';
const calculateTheRest = () => {
if (!setup.material.touched && !setup.laborAndMaterial.touched ||
!setup.labor.touched && !setup.laborAndMaterial.touched ||
!setup.labor.touched && !setup.material.touched) {
return false; // two unknowns, can't recalculate
}
if (!setup.labor.touched || fixed !== 'labor' && prevFixed !== 'labor') {
setup.labor.value = setup.laborAndMaterial.value - setup.material.value;
} else if (!setup.material.touched || fixed !== 'material' && prevFixed !== 'material') {
setup.material.value = setup.laborAndMaterial.value - setup.labor.value;
} else {
setup.laborAndMaterial.value = setup.material.value + setup.labor.value;
}
}
const $els = {};
Object.keys(setup).forEach(key => $els[key] = document.querySelector('#' + key))
const onInputChanged = (e) => {
const key = e.target.id;
setup[key].value = +e.target.value;
setup[key].touched = true;
if (fixed !== key) {
prevFixed = fixed;
fixed = key;
}
calculateTheRest();
Object.keys(setup).forEach(key => $els[key].value = setup[key].value);
}
Object.keys(setup).forEach(key => {
$els[key].value = setup[key].value; // initial set
setup[key].touched = setup[key].value !== 0; // 0 on initial setup are the numbers that not set
$els[key].addEventListener('input', onInputChanged);
})
<p><label>labor: <input id="labor" type="number"/></label></p>
<p><label>material: <input id="material" type="number"/></label></p>
<p><label> laborAndMaterial: <input id="laborAndMaterial" type="number" /></label></p>
I think you need to implement a condition on your laborAndMaterial field.
Check the condition: -
if(labor > 0 && material > 0){
let laborAndMaterial = labor + material;
}
And after that set the laborAndMaterial variable value into field,
I think it will may help you.
https://snack.expo.io/#miralis/bad-apple
Check pressNumber. I want to Alert.alert("Nice work!") when "if" returns true, and ("Try again!") when it returns false, but Alert.alert() doesn't work.
It seems to work like #blankart said, I guess just because you declare a random num and then you can't catch the correct number each time. Just change the num to a real number like this and input it. You can go into the true statement.
PS:You could console out the num and number to check the original statement.
const [number, setNumber] = useState();
const pressNumber = () => {
const num = (Math.random() * 1).toFixed(); //random each time is different.
//if(num == number) {
if(number == 5) { // and then input 5 is the correct to nice work.
console.log("Nice work!");
Alert.alert("Nice work!")
}
else {
console.log(num) // console the num
console.log(number) // console the number
console.log("Try again!");
Alert.alert("Try again!")
}
}
I am building a two player game in react/redux. There is always one player whose turn it is. The turn needs to change regularly between the two.
I have the following reducer. It works once, and then stops working.
case CHANGE_TURN:
if (playerTurn === 1) {
newPlayerTurn = 2;
newOtherPlayer = 1;
}
else {
newPlayerTurn = 1;
newOtherPlayer = 2;
}
return {
...state,
otherPlayerID: newOtherPlayer,
playerTurnID: newPlayerTurn
}
Since this part has worked in the past, I think perhaps something else is getting in the way????
I think the answer was accepted but you can also minimize your swapping operation by using:
[newPlayerTurn, newOtherPlayer] = [newOtherPlayer, newPlayerTurn];
Cloned your repo and checked it out.
Your players.js reducer, line 5 is:
let playerTurn = players.playerTurnID;
But should look like:
let playerTurn = state.playerTurnID;
You should get your playerTurn from your state object.
You might have forgotten to alternate playerTurn between 1 and 2 by returning it in the state. Also, the case can be simplified:
case CHANGE_TURN:
return {
...state,
playerTurn: state.playerTurn === 1 ? 2 : 1,
otherPlayerID: state.playerTurn === 1 ? 1 : 2,
playerTurnID: state.playerTurn === 1 ? 2 : 1,
};
playerTurn and playerTurnID are redundant btw, but maybe your example was simplified.
If it's strictly always exactly two players, one boolean is enough to model the entire state:
case CHANGE_TURN:
return { activePlayer: !state.activePlayer };
With an initialState of { activePlayer: false }. You could then write selectors like
const isPlayer1Active = (state) => !state.activePlayer; // false = player1's turn
const isPlayer2Active = (state) => state.activePlayer; // true = player2's turn
const getActivePlayerID = (state) => state.activePlayer ? 2 : 1;