Call a function at last iteration of a .map - javascript

I want to execute a function received by props at the last iteration of a map loop,
I tried to execute the function when index is equal to array.length - 1, but it stills execute the function at every iteration, so the console.log("last index") appears at every iteration,
The idea is to execute this.props.handleTotalPrice with totalPrice as parameter once I'm done to increment totalPrice at every iteration,
I tried the following code :
products && products.map((product, index) => {
const cost = Math.round((product.price * planningData[product.productId][planningType]) * 100) / 100;
nbCount += parseInt(planningData[product.productId][planningType]);
totalPrice += cost;
if (index === products.length - 1) {
console.log("last index")
this.props.handleTotalPrice(totalPrice)
}
});

Since you can use reduce instead of map to iteratively run up a sum, then return the value, and do something with the result.
products &&
this.props.handleTotalPrice(
products.reduce((totalPrice, product) => {
const cost = Math.round((product.price * planningData[product.productId][planningType]) * 100) / 100;
nbCount += parseInt(planningData[product.productId][planningType]);
totalPrice += cost;
return totalPrice;
}, 0)
)

The most efficient way is to do the callback after the map has completed instead of doing it at the last iteration.
{products && products.map((product, index) => {
const cost = Math.round((product.price * planningData[product.productId][planningType]) * 100) / 100;
nbCount += parseInt(planningData[product.productId][planningType]);
totalPrice += cost;
}) && this.props.handleTotalPrice(totalPrice);
Notes:
Using Array.map without returning anything in the callback is the same as using a Array.forEach
Writing JSX in React
It's usually recommended to only do simple operations in the render function, having a callback (like handleTotalPrice) in the render function is not recommended and can lead to bugs or huge performance issues.
Assuming you are using a React version that supports hooks, I would change the code like this:
const MyComponent({ handleTotalPrice }) {
const products = {/* your data*/};
// Compute total price outside render function
// Use `useMemo` so price is only recalculated if products list changes
const totalPrice = useMemo(() => {
if (!products) return 0;
const total = products.reduce((sum, product) => {
const cost = Math.round((product.price * planningData[product.productId][planningType]) * 100) / 100;
return sum + cost;
});
// Call the callback when price changes
handleTotalPrice(total);
}, [products]);
// Render
return <div>
{totalPrice}
</div>;
}

Related

Im trying to cycle through an array an display each item one by one with an interval of one hour

So this is the code that I wrote :
var array = [image1, image2, image3, image4, image5];
array.forEach(function(obj, num, arr){
function SH(){var random = Math.ceil(Math.random() * array.length); setTimeout(array[random].style="opacity:1;transition:0.5;", 3600000 * random);
setTimeout( array[random].style="opacity:0; transition:0.5;", 3600000 * random + 1);};
SH();
setInterval(SH(), 3600000 * arr.length )
});
I want to make a random Image visible for an hour and than stop displaying it and make the next one visible. Im getting the "Cannot set property style of undefined" error. How do I fix it?
var random = Math.ceil(Math.random() * Array.length); returns undefined.
It's because Array is a keyword in JS. Rename it to array
Thanks for the comment. There are actually multiple problems in that code. I wanted to give you the next step how to fix it. There is also an incorrectly defined setTimeout callback and you should also use length - 1
If you'd like a fixed version, here you go:
var array = ["image1", "image2", "image3"];
array.forEach(function (obj, num, arr) {
function SH() {
var random = Math.ceil(Math.random() * array.length - 1);
setTimeout(() => {
array[random].style = "opacity:1;transition:0.5;";
}, 3600000 * random);
setTimeout(() => {
array[random].style = "opacity:0; transition:0.5;";
}, 3600000 * random + 1);
}
SH();
setInterval(SH(), 3600000 * array.length );
});
After comments:
var array = ["image1", "image2", "image3"];
function SH() {
var random = Math.ceil(Math.random() * array.length - 1);
array.forEach(function (obj, num, arr) {
// if any other index than randomised one
if (num !== random) {
array[random].style = "opacity:1;transition:0.5;";
} else {
array[num].style = "opacity:0; transition:0";
}
});
}
SH(); // start first
setInterval(() => SH(), 3600000); // set the interval

Getting infinity loop while checking factors of a number in while loop

I am struggling with infinite loop problem while Array exercise implementation which needs to be done with Java Script functional way:
I have a code which creates an array and fills its values with numbers which fulfil condition:
Each array element has a value,
which we draw from the range <100, 200> until the sum of digits is
a number having exactly two dividers, not counting 1 and this one
numbers.
I have a code like below:
const generateNumber = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + Math.floor(min);
const unities = number => number % 10;
const hundreds = number => Math.floor((number % 1000) / 100);
const tens = number => Math.floor((number % 100) / 10);
const sumDigits = (number) => unities(number) + hundreds(number) + tens(number);
const countNumberFactors = number => Array
.from(Array(number + 1), (_, i) => i)
.filter(i => number % i === 0)
.slice(1, -1)
.length;
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max)
}
return number;
}
const generateArray = (minArrSize, maxArrSize, minItemValue, maxItemValue) =>
Array(generateNumber(minArrSize, maxArrSize))
.fill(0)
.map(
() => generateNumberUntilConditionNotAchieve(minItemValue,
maxItemValue));
const main = () => {
const generatedArray = generateArray(1, 5, 100, 200);
console.log("Array -> " + generatedArray);
}
main();
For small minArraySize and maxArraySize values sometimes I am receiving desirable result but for params like <10, 100> my IDE is freezing. On online editor with pasted above code, I am receiving information about the infinite loop on line:
while (countNumberFactors(digitsSum) === 2)
I tried to investigate a root cause by trial and error but I did not find out a solution. I will be grateful for suggestions on how to solve the above infinite loop problem.
You are changing number but checking digitsSum. All you need to do to fix this is add digitsSum = sumDigits(number) in the while loop. e.g.
const generateNumberUntilConditionNotAchieve = (min, max) => {
let number = generateNumber(min, max);
const digitsSum = sumDigits(number);
while (countNumberFactors(digitsSum) === 2) {
number = generateNumber(min, max);
digitsSum = sumDigits(number);
}
return number;
}

Problem updating a state using setInterval in React application

I have a function called getRandomHexagram() which returns a random 6-character string that is used for props in my <hexagram string={stringstate} /> component in my I Ching divination app.
I also have a function called resettimer() which retrieves this random 6-character string multiple times and passes it to my component within a few seconds, so that while setInterval is running, the component will keep changing its appearance until clearInterval is called.
I would like to continuously update the state using resettimer() function run until it ends, and then use the new state in another function together with clearInterval.
What is strange to me is that while the if portion of the timer is running, the component continuously updates as it should. But when I try to call the final state in the } else { statement together with clearInterval, console always shows that stringstate is still "VVVVVV", or whatever was the last prop that my component used in my previous click, NOT what my component is currently using.
function getRandomHexagram() {
const flipcoins = () => {
return (
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2) +
(Math.floor(Math.random() * 2) + 2)
);
};
const drawline = (coinvalue) => {
let line;
if (coinvalue == 6) {line = "B";}
if (coinvalue == 7) {line = "V";}
if (coinvalue == 8) {line = "P";}
if (coinvalue == 9) {line = "W";}
return line;
};
return (
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins()) +
drawline(flipcoins())
);
}
function App() {
const [stringstate, setStringstate] = useState("VVVVVV");
function resettimer() {
var timesrun = 0;
var randomtime = Math.floor(Math.random() * 15) + 10;
const interval = setInterval(() => {
timesrun += 1;
if (timesrun < randomtime) {
thisString = getRandomHexagram();
setStringstate(thisString);
console.log("the current permutation is: " + thisString);
// console and component shows this correctly.
} else {
clearInterval(interval);
console.log("the final state state is: " + stringstate);
// Call another function here using the NEW stringstate
// But console shows that stringstate is still showing
// the old state of VVVVVV despite my component showing otherwise
}
}, 100);
}
... ...
return (
<div className="App">
<Button onClick={() => resettimer()}>
<Hexagram string={stringstate} />
</div>
);
}
When the button is clicked and the function invoked, my console shows this:
the current permutation is: VPPVPB
the current permutation is: VPPPVV
the current permutation is: BPBVBV
the current permutation is: VVPVPV
the current permutation is: PPBVPV
the current permutation is: PPPBBB
the current permutation is: VVPPPV
the final state state is: VVVVVV
Any idea why this is happening? Any advice is much appreciated.

Break out of map

so i have this problem where if the value in the array is higher than entered value it should do something and then stop the loop and don't touch the remaining values in the array.. Here's the code so far:
const percentages = [];
let enteredValue = parseInt(event.target.value, 10);
range.map((rangeValue, i) => {
if (rangeValue <= enteredValue) {
percentages.push(100);
enteredValue = enteredValue - rangeValue;
} else {
percentages.push(enteredValue * 100 / (rangeValue));
return;
}
});
Using .some you can get iteration functionally similar to .forEach, map or for loop but with the ability to break through return instead.
range.some(function(rangeValue , i) {
if (rangeValue <= enteredValue) {
percentages.push(100);
enteredValue = enteredValue - rangeValue;
return true
}
percentages.push(enteredValue * 100 / (rangeValue));
});
Read more about .some in es6 here
Just use a good old for loop:
const percentages = [];
let enteredValue = parseInt(event.target.value, 10);
for(const range of ranges) {
percentages.push(Math.min(100, (enteredValue / range) * 100));
enteredValue -= range;
if(enteredValue <= 0) break;
}
The other answers are perfectly sufficient. However, I would like to add that the map function isn't best used for performing iterations. What the map function does is perform the as argument passed in callback on every element of the array. It then returns the new array. So map is more usefull when you are in need of a mutated copy of the Array (e.g. all the elements multiplied by 10).
For your purposes other logic like a regular for loop will be a more elegant solution.
You can use reduce instead of map. The reduce senses a change in the original array.
let enteredValue = parseInt(event.target.value, 10);
const percentages = range
.slice(0) // if you don't want to mess up the original array
.reduce((percentages, rangeValue, _, rangeCopyArray) => {
if (rangeValue <= enteredValue) {
percentages.push(100);
enteredValue -= rangeValue;
} else {
percentages.push(enteredValue * 100 / rangeValue);
rangeCopyArray.length = 0; // the loop ends here
}
return percentages;
}, [])
or shorter but less clear
const percentages = [...range].reduce(({p, eV}, rV, _, arr) => ({
eV: eV-rV,
p: [...p, rV <= eV ? 100 : eV*100/rV + (arr.length = 0)]
}), {p: [], eV: parseInt(event.target.value, 10)}).p;

Sized array of random unique numbers

I was wondering what was the most concise way to get an array of a certain size, of unique random numbers.
I get random numbers like this:
times(4, () => random(30, 95));
However this is not unique. I can filter this with uniq but I need to gurantee length of 4 of array. And I want to do it the lodash way. Any ideas?
Much easiear would be...
const uniqRandomNumbers = _.sampleSize(_.range(30, 95), 4);
console.log(uniqRandomNumbers);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
I know this isn't "the lodash way", but it guarantees uniqueness, and allows you to use the same arguments as you were using before. It also scales better than methods that require a binary or linear search through an array, as set.has() is O(1) on average, rather than O(log(n)) or O(n).
function uniqRandom (times, ...args) {
const set = new Set()
while (times > 0) {
const rand = _.random(...args)
if (!set.has(rand)) {
set.add(rand)
times--
}
}
return Array.from(set)
}
console.log(uniqRandom(4, 30, 33));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
I solved it using from a functional programming perspective. refillRandom is a recursive function that checks the number of items left to generate and calls itself again until the are the required number of items.
It also throws an Error when is imposible to generate the sequence, if the distance between min and max random number is greater than the required unique items. It's better to throw an Error than waiting forever.
const generator = (min, offset) => () =>
Math.floor(Math.random() * offset + min);
const refillRandom = (list, min, max, times) => {
const offset = max - min,
num = times - list.length;
if (times > offset) {
throw new Error("Imposible to generate it");
}
const result = _.uniq(_.times(num, generator(min,offset)));
if (result.length < num) {
return result.concat(
refillRandom(list, min, max, num - result.length)
);
}
return result;
}
const r = refillRandom([], 30, 95, 4);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
EDIT: I found another solution, I mantain an ordered array of generated numbers and increment the generated number so it get mapped to a number that has not been generated yet. This way I only call random the times specified.
const randomGenerator = (min, offset, generated, times) => {
if (!times || !offset) return generated;
var number = Math.floor(Math.random() * offset + min);
const len = generated.length;
for (var i = 0; i < len; i++) {
if (generated[i] <= number) {
number++;
} else {
generated.splice(i, 0, number);
return randomGenerator(min, offset - 1, generated, times - 1);
}
}
generated[i] = number;
return randomGenerator(min, offset - 1, generated, times - 1);
};
const r = randomGenerator(30, 95 - 30, [], 12);
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>

Categories