split a number by total number onChange input - arrays

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.

Related

How to create custom input field with autospaces for entering vehicle number plate

I need to devlop input field for vehicle number entry but the only problem I am facing when press backspace the logic breaks need help in this..
React.useEffect(()=>{
if(cardNumber.length===2)
setCardNumber(cardNumber+" ")
else if (cardNumber.length === 5) {
setCardNumber(cardNumber+" ")
} else if (cardNumber.length === 8) {
setCardNumber(cardNumber+" ")
}
}, [cardNumber]);
Move your logic from useEffect hook to onChange and try following:
const onChange = (event) => {
const value = event.target.value;
if ([2, 5, 8].includes(value.length)) {
setCardNumber((prev) => {
// this is only the case when we try to delete empty space
if (prev.endsWith(" ")) {
return value.slice(0, -1);
} else {
return value + " ";
}
});
} else {
setCardNumber(value);
}
};
There are many possible solutions, so this is just a one of them. Basically the idea is to read previous state and check if the value ends with an empty space.
https://codesandbox.io/s/compassionate-grass-krhwxh

How can I adjust my formula to continuously autofill the missing paramater?

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.

How is useState() updating my data here? STRANGE

I have data from an movie-api I want to sort based on a select menu, either by year in descending order or title in alphabetical order.
Although Im only updating state in the sort function, not using the variable any where, the data thats already mapped out, and in a different array, updates accordingly. I guess its somehow related to the first change in state for each of the two differen variables?
Any idea how I should solve this correctly and why this is happening?
const sortData = (e) => {
if (e === "year"){
const yearData = data.sort(function(a, b) {
const yearA = a.Year;
const yearB = b.Year;
if (yearA < yearB) {
return -1;
}
if (yearA > yearB) {
return 1;
}
return 0;
});
setYearData(yearData);
}
if (e === "title") {
const titleData = data.sort(function(a, b) {
const titleA = a.Title.toUpperCase();
const titleB = b.Title.toUpperCase();
if (titleA < titleB) {
return -1;
}
if (titleA > titleB) {
return 1;
}
return 0;
});
setTitleData(titleData);
}
}
The sort() method sorts the elements of an array in place, so the data(state) changed without using setState (It may cause some unpredictability happened in the execution)
You can use the sort() method on a copy of the array, so it doesn't affect your state array, I guess this change may work:
use [...data].sort(...) instead of data.sort(...)
Array.sort(), in your case data.sort() updates the original array in addition to returning it. Seems like your data variable is some sort of global that gets changed during sort().

React Hooks - Infinite loops when setting a state within a map

For reference: I am working on a sliding puzzle game.
So I have a const Board function and I have defined 2 states named:
puzzlePieces
hiddenIndexNumber
The point of 'hiddenIndexNumber' is to keep track of the hidden block index within the game. So before the game starts, I loop through a new array that I create for puzzlePieces and use map to return HTML elements. When looping, I want to make sure that I get the hidden block index for my hiddenIndexNumber to keep track of the hidden block.
This is how my code (partially) looks:
const Board = () => {
const totalPieces = 9
const hiddenNumber = totalPieces
const[hiddenIndexNumber, setHiddenIndex] = useState(-1)
// here I create an array of 9 elements and shuffle them with underline
const [puzzlePieces, changePuzzlePieceContent] = useState(
_.shuffle( [ ...Array( totalPieces ).keys() ].map( num => num + 1 ) )
)
let puzzleElements = [ ...Array( totalPieces ).keys() ].map( index => {
// the problem here is that setHiddenIndex makes the framework rerender
// the DOM after setting the index number and I don't know how to solve the issue here
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
return <Puzzle
key = { index }
index = { index }
number = { puzzlePieces[index] }
hidden = { puzzlePieces[index] === hiddenNumber && true }
onChange = { handleChange }
/>
} )
}
The problem is with this code:
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
How do I make sure that I set hiddenIndexNumber without requesting for rerendering the DOM?
I would suggest you to look into shouldComponentUpdate() and useEffect() Hooks.
It's well described here: shouldComponentUpdate()? with an example.

Trying to find the sum of checked values in a table using react

This function currently returns the total score, but I trying to get only the sum of the checked value (score). So, each time the user selects the checkbox, the function will return the sum of the score.
"Score" is one of the three columns in a table. I am using checkboxes to select.
calculateTotal=(e)=> {
const { input, checked, type} = e.target;
let sumVal= 0;
this.props.items.forEach((item, i)=> {
if(checked === true){
( sumVal += item.score)
}
})
this.setState(sumVal => sumVal)
console.log(sumVal);
document.getElementById("val").innerHTML = "Sum Value =" + sumVal;
}
When you give function in this.setState, function gets previous state as parameter. You are essentially returning same state and thus nothing is changed.
calculateTotal=(e)=> {
const { input, checked, type} = e.target;
let sumVal= 0;
this.props.items.forEach((item, i)=> {
if(checked) sumVal += item.score
})
this.setState(prevState=>({...prevState, sumVal}));
// this.setState({sumVal}) ; // also works
console.log(sumVal);
document.getElementById("val").innerHTML = "Sum Value =" + sumVal;
}
Please let me know if I missed something.

Resources