my event doesn't fire only when its called - javascript

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>

Related

Intl.NumberFormat Returning NAN values

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.

Problem with Animating Number Counter on Javascript

I was hoping to create a counter which would increment to the eventual number of followers. I have other code, but I just included the part that would affect the incrementation. Currently, when I run the program the HTML file only displays the final number of followers, and the word "Followers" showing it is able to get the proper number, but it isn't able to have an incrementation animation as I would like. How would I go about fixing this?
var text_field = document.getElementById('followers');
fetch(api)
.then((response) => {
return response.json();
})
.then((data) => {
var number = data['Count'][0][0];
const updateCount = () => {
const target = number;
const count = text_field.innerText;
const increment = target / speed;
if (count < target) {
text_field.innerText = count + increment;
setTimeout(updateCount, 5);
} else {
text_field.innerText = target + ' Followers';
}
};
updateCount();
});
The innerText property returns a string value. Use the parseInt function before any calculations.
var text_field = document.getElementById("followers");
function counter(){
var number = 100;
const updateCount = () => {
const target = number;
const count = parseInt(text_field.innerText); //parsing
const speed=number;
const increment = target / speed;
if (count < target) {
text_field.innerText = count + increment;
setTimeout(updateCount, 5);
} else {
text_field.innerText = target;
}
};
updateCount();
}
counter();
<div id="followers">1</div> Followers!!!

useAnimationOnDidUpdate react hook implementation

So I want to use requestAnimationFrame to animate something using react hooks.
I want something small so react-spring/animated/react-motion is a bad choice for me.
I implemented useAnimationOnDidUpdate but it is working incorrectly, here is reproduction with details.
What's wrong here: on second click multiplier for animation starts with 1, but should always start with 0 (simple interpolation from 0 to 1).
So I'm trying to understand why the hook saved previous value though I started a new animation loop already.
Here is a full code listing for hook:
import { useState, useEffect, useRef } from 'react';
export function useAnimationOnDidUpdate(
easingName = 'linear',
duration = 500,
delay = 0,
deps = []
) {
const elapsed = useAnimationTimerOnDidUpdate(duration, delay, deps);
const n = Math.min(1, elapsed / duration);
return easing[easingName](n);
}
// https://github.com/streamich/ts-easing/blob/master/src/index.ts
const easing = {
linear: n => n,
elastic: n =>
n * (33 * n * n * n * n - 106 * n * n * n + 126 * n * n - 67 * n + 15),
inExpo: n => Math.pow(2, 10 * (n - 1)),
inOutCubic: (t) => t <.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
};
export function useAnimationTimerOnDidUpdate(duration = 1000, delay = 0, deps = []) {
const [elapsed, setTime] = useState(0);
const mountedRef = useRef(false);
useEffect(
() => {
let animationFrame, timerStop, start, timerDelay;
function onFrame() {
const newElapsed = Date.now() - start;
setTime(newElapsed);
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
return;
}
loop();
}
function loop() {
animationFrame = requestAnimationFrame(onFrame);
}
function onStart() {
console.log('>>>> start with time', elapsed)
start = Date.now();
loop();
}
if (mountedRef.current) {
timerDelay = delay > 0 ? setTimeout(onStart, delay) : onStart();
} else {
mountedRef.current = true;
}
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
};
},
[duration, delay, ...deps]
);
return elapsed;
}
This problem with this hook is that it doesn't clean up the elapsedTime upon completion.
You can resolve this by adding setTime(0) to you onFrame function when the animation is expected to stop.
Like this:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
I know it may seem weird that it doesn't reset itself. But bear in mind that your animation is making use of the same hook instance for both easing in and out. Therefore that cleanup is necessary.
Note: I've also move the setTime(newElapsed) line so that it's after the if statement since this isn't necessary if the if statement is true.
UPDATE:
To further improve how this works, you could move the setTime(0) to the return cleanup.
This would mean that you're onFrame function changes to:
function onFrame() {
const newElapsed = Date.now() - start;
if (newElapsed >= duration) {
console.log('>>>> end with time', newElapsed)
setTime(0)
return;
}
setTime(newElapsed);
loop();
}
And then update your return cleanup for useAnimationTimerOnDidUpdate to:
return () => {
clearTimeout(timerStop);
clearTimeout(timerDelay);
cancelAnimationFrame(animationFrame);
setTime(0);
};
I'm assuming that the reason your animation "isn't working properly" was because the component would flash. As far as my testing goes, this update fixes that.

How to calculate waypoints between multiple waypoints?

So for example I have an array with 3 waypoints:
[ [ 526, 1573, 24 ], [ 2224, 809, -1546 ], [ 6869, 96, -3074 ] ]
I also know I want to rest for lets say n times between between arriving at the first and last waypoint. So in the end I want an array of n points.
How do I go about finding those n resting-points in JS?
Thanks in advance!
EDIT: Note this is not a single object! Imagine each axis being one person. They have to stop the same amount of time and at the same time but they do not have to be at the same place obviously.
You want to use linear interpolation.
A quick example:
const POINTS = [ [ 526, 1573, 24 ], [ 2224, 809, -1546 ], [ 6869, 96, -3074 ] ];
const N = 10;
function getDistance(point1, point2) {
// speed in 3d space is mutated according only to the X distance,
// to keep speed constant in X dimension
return Math.abs(point1[0] - point2[0]);
}
function go(points, n) {
const pointDistances = points.slice(1).map((point, index) => getDistance(points[index], point));
const fullDistance = pointDistances.reduce((sum, distance) => sum + distance, 0);
const distancePerSection = fullDistance / n;
return points.slice(1)
.reduce((last, point, index) => {
const thisDistance = pointDistances[index];
const numRestPoints = Math.max(0, Math.floor(thisDistance / distancePerSection) - 1);
if (!numRestPoints) {
return last.concat([point]);
}
const thisYVector = point[1] - points[index][1];
const thisZVector = point[2] - points[index][2];
return last.concat(new Array(numRestPoints).fill(0)
.reduce((section, item, restIndex) => {
return section.concat([[
points[index][0] + (restIndex + 1) * distancePerSection,
points[index][1] + (restIndex + 1) * thisYVector * distancePerSection / thisDistance,
points[index][2] + (restIndex + 1) * thisZVector * distancePerSection / thisDistance
]]);
}, [])
.concat([point])
);
}, points.slice(0, 1));
}
function test() {
const result = go(POINTS, N);
if (result.length !== N) {
throw new Error('Must be N length');
}
if (!result[0].every((value, index) => value === POINTS[0][index])) {
throw new Error('Doesn\'t start at the first point');
}
if (!result[N - 1].every((value, index) => value === POINTS[POINTS.length - 1][index])) {
throw new Error('Doesn\'t end at the last point');
}
if (!POINTS.slice(1, N - 1).every(point =>
result.some(resultPoint => resultPoint.every((value, index) => value === point[index]))
)) {
throw new Error('Doesn\'t go through every provided point');
}
console.log(result.slice(1).map((point, index) => getDistance(point, result[index])));
console.log('The result passed the tests!');
console.log(JSON.stringify(result, null, 2));
}
test();
I'm basically going through the list of points, and determining if there should exist any rest points between them, inserting them if so.
Please comment if you want further clarification!
I also solved this problem now with linear interpolation:
My solution:
var waypoints = [[526,1573,24],[2224,809,-1546],[6869,96,-3074]];
var pauses = 20;
generateWaypopints();
function generateWaypopints(){
var newWaypoints = [];
var progressAtMainPoints = 1 / (waypoints.length - 1)
var pausesBetweenWaypoints = pauses * progressAtMainPoints;
var progressAtPauses = 1 / pausesBetweenWaypoints;
newWaypoints.push(waypoints[0]);
var sector = 0;
var pausesInSector = 0;
for(var i = 0; i < pauses; i++){
var progress = progressAtPauses * (pausesInSector + 1)
var x = Math.round(waypoints[sector][0] + (waypoints[sector + 1][0] - waypoints[sector][0]) * progress);
var y = Math.round(waypoints[sector][1] + (waypoints[sector + 1][1] - waypoints[sector][1]) * progress);
var z = Math.round(waypoints[sector][2] + (waypoints[sector + 1][2] - waypoints[sector][2]) * progress);
if(progress >= 1){
sector++;
pausesInSector = 0;
}else
pausesInSector++;
newWaypoints.push([x,y,z]);
}
console.log(newWaypoints);
return newWaypoints;
}

JS - How to repetativly fire a function at random time for certain period

I would like to imitate progress bar getting random lengh added 0% to 100% with series of messages displayed in certain sequence but also in random time.
In order to do that I'm staring two independent loops using JS setTimeout method with a random number genearted inside of each loop. But each loop not supposed to be longer than 30 seconds and with the way I'm doing I can't control that.
For the second loop I have to fire the function for 11 times randomly but fit that into 30 seconds as well.
The progress bar moves by changing its width. The messages supposed to be right above that. It looks like this:
HTML:
<span id="progressMsg"></span>
<input type="hidden" id="progressMsgNumber">
<div class="pogress progress-info slim" id="progressBar" style="width=1%"></div>
JS:
setTimeout(progressBar, 1000);
setTimeout(progressMsn, 1000);
function progressBar() {
barWidth = document.getElementById("progressBar").style.width.slice(0, -1); //to get number without "%" char
if (barWidth >=100) {
// We are done
} else {
minRand = 1;
maxRand = 5;
randSeconds = Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand;
barWidth = Number(barWidth)+randSeconds;
document.getElementById("progressBar").style.width = barWidth+"%";
setTimeout(progressBar, randSeconds*1000);
}
}
function progressMsg() {
msgList = new array ("Msg1", "Msg2", ... "Msg11");
barWidth = document.getElementById("progressBar").style.width.slice(0, -1);
if (barWidth >=100) {
// We are done
} else {
msgNumber = document.getElementById("progressMsgNumber").val;
msgNumber++;
document.getElementById("progressMsg").innerHTML = msgList[msgNumber];
document.getElementById("progressMsgNumber").val = msgNumber;
minRand = 1;
maxRand = 5;
randSeconds = Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand;
setTimeout(progressMsg, randSeconds*1000);
}
}
The key is to create ALL the random times beforehand, then you can manipulate the times in such a way as to guarantee exact 30 seconds total time.
Note: code below, for demo purposes, has times 1/10th of what you would use, i.e. you'd want
minRand = 1000,
maxRand = 5000,
maxTime = 30000;
function progressMsg() {
const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
minRand = 100,
maxRand = 500,
maxTime = 3000;
let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
const mult = maxTime / triggers.slice(-1);
triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
const update = (t, i) => {
console.log(t, Math.round(t/maxTime * 100) + '%', msgList[i]);
}
triggers.forEach((n, i) => setTimeout(update, n, n, i));
}
progressMsg();
to make it work with your code
function progressMsg() {
const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
minRand = 1000,
maxRand = 5000,
maxTime = 30000;
// create an array of random times, e.g. 1,3,4,2,1...
let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
// accumulate times, so we'd have e.g. 1,4,8,10,11...
triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
const mult = maxTime / triggers.slice(-1);
// adjust so the last time is exactly maxTime
triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
const update = i => {
document.getElementById("progressMsg").innerHTML = msgList[i];
document.getElementById("progressMsgNumber").val = i;
}
// start all the timeouts at once, because we have cumulative times
triggers.forEach((n, i) => setTimeout(update, n, i));
}
function progressBar() {
const minRand = 1000,
maxRand = 5000,
maxTime = 30000,
bar = document.getElementById("progressBar");
let time = 0;
let triggers = [];
// here we just keep adding random times until we reach maxTime
while (time < maxTime) {
triggers.push(time += Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
}
const mult = maxTime / triggers.slice(-1);
// adjust times so last time is exactly maxTime
triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
const update = t => {
bar.style.width = (100*t/maxTime) + '%';
}
triggers.forEach(n => setTimeout(update, n, n));
}
In answer to comment - one way to know when all is done is using promises
const delay = (fn, delay, ...params) => new Promise(resolve => setTimeout(fn, delay, ...params));
function progressMsg() {
const msgList = ["Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9", "Msg10", "Msg11"],
minRand = 1000,
maxRand = 5000,
maxTime = 30000;
// create an array of random times, e.g. 1,3,4,2,1...
let triggers = Array.from({length:11}, () => Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
// accumulate times, so we'd have e.g. 1,4,8,10,11...
triggers.forEach((n, i, a) => a[i] = (a[i-1]||0) + a[i]);
const mult = maxTime / triggers.slice(-1);
// adjust so the last time is exactly maxTime
triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
const update = i => {
document.getElementById("progressMsg").innerHTML = msgList[i];
document.getElementById("progressMsgNumber").val = i;
}
// start all the timeouts at once, because we have cumulative times
return Promise.all(triggers.map((n, i) => delay(update, n, i)));
}
function progressBar() {
const minRand = 1000,
maxRand = 5000,
maxTime = 30000,
bar = document.getElementById("progressBar");
let time = 0;
let triggers = [];
// here we just keep adding random times until we reach maxTime
while (time < maxTime) {
triggers.push(time += Math.floor(Math.random() * (maxRand - minRand + 1)) + minRand);
}
const mult = maxTime / triggers.slice(-1);
// adjust times so last time is exactly maxTime
triggers = triggers.map(v => Math.floor(Math.ceil(v * mult)));
const update = t => {
bar.style.width = (100*t/maxTime) + '%';
}
return Promise.all(triggers.map(n => delay(update, n, n)));
}
// usage
Promise.all([progressMsg(), progressBar()]).then(() => {
// all done here
});
you can store the full spent time and number of displayed messages in variables
let spentTime = 0; // Declare
let displayedMessages = 0;
function progressMsg() {
....
setTimeout(() => { // Edit
progressMsg();
spentTime += randSecods; // add the seconds of this tick
displayedMessages++;
}, randSeconds*1000);
}
}
you can then use it to change the min and max in any tick passed on the spent time and the left messages
something like
maxSeconds = (fullTime - spentTime) / (numOfMessages - displayedMessages)
will make sure all the messages are displayed, you can change the min value too, to seem more naturally.
You can wrap your functions into Promise, with one additional Promise to resolve itself in 30 seconds, and then use Promise.race
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race

Categories

Resources