Problem updating a state using setInterval in React application - javascript

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.

Related

Call a function at last iteration of a .map

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>;
}

Handle back/forward button click in browser

I want to console.log(1) if the user clicks on the forward button inside browser,
-1 if the user clicks on back button
0 if user reloads the page,
This is my code so far, I can not achieve to get -1, why am I doing wrong??
My approach is setting a state inside history object in the component and compare it vs the lenght of the history object...
async function handleBackForwardButton() {
// If none, then zero
const historyLength = Number(sessionStorage.getItem("historyLength"));
let { state } = await window.history.state;
let position;
if (state === null || state === undefined) {
// New entry on the stack
position = historyLength + 1;
debugger;
// Stamp the entry with its own position in the stack
window.history.replaceState(historyLength, /*no title*/ "");
// (2) Keep track of the last position shown
sessionStorage.setItem("historyLength", String(window.history.length));
debugger;
// (3) Discover the direction of travel by comparing the two
const direction = Math.sign(position - historyLength);
console.log("Forward should be (1) :" + direction);
debugger;
// forward should be (1)
} else if (historyLength > state) {
const direction = Math.sign(state - historyLength);
console.log("Backward should be (-1) :" + direction);
debugger;
//One of backward (-1)
} else if (historyLength === state) {
const direction = Math.sign(state - historyLength);
console.log("Reloading page should be (0) : " + direction);
debugger;
//Reloading page should be (0)
}
}
window.addEventListener("pageshow", handleBackForwardButton);
window.addEventListener("popstate", handleBackForwardButton);

How to recursively change color of element

I want to create a program that recursively changes the color of text.
I've already created a for(){} loop with the random color code in it in order to attempt to recurse it.
for(var x = 0; x > -1; x++){
document.getElementById("k").style.color = '#'+(0x1000000+
(Math.random())*0xffffff).toString(16).substr(1,6)}
<p id="k">Yeet</p>
The actual result is that the color stays at it's default, black. I would like it to be that every time it runs (infinitely), it will change colors.
You have to use setInterval() method to run asynchronously without blocking the main execution.
setInterval(() => document.getElementById("k").style.color = '#' + (0x1000000 +(Math.random()) * 0xffffff).toString(16).substr(1, 6),500)
<p id="k">Yeet</p>
If you want to stop at some point then use clearInterval() method to clear the interval.
let i = 0;
const inter = setInterval(() => {
document.getElementById("k").style.color = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
// to clear after 10 colors
if (i++ === 10) clearInterval(inter);
}, 500)
<p id="k">Yeet</p>
/** Bad recursive pattern.
*
* All recursive methods should have a base case,
* I assume you want the change to stop at some point,
* if not you have an infinite loop running.
*
* This function can still time out if it takes too long to hit "theLastColor"
*/
function recursiveColorChange(id, currentColor) {
const theLastColor = '#some-value';
if (currentColor === theLastColor) {
return;
}
const randomColor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
document.getElementById(id).style.color = randomColor;
return recursiveRandomColor(id, randomColor);
}
However, using the pure recursive code prevents controlling color change Z_BEST_SPEED.
As mentioned by #pranav-c-balan, I think it is better to use setTimeout.
You can still have a base case where you stop changing colors by using clearTimeout();
/** Better setTimeOut pattern.
*
* You can pass a lastColor value if you want it to stop if it reaches a color.
* Or you can just pass it an id and a speed (in milliseconds) and it will run forever without breaking your code.
*/
function repeatColorChange(id, speed, lastColor) {
const myTimeOut = setTimeout(() => {
const randomColor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
document.getElementById(id).style.color = randomColor;
if (randomColor === lastColor) {
clearTimeout(myTimeOut);
}
}, speed);
}

How to find previous randomly generated number using Math.random in a setInterval function?

I am generating a random number between 1 and 13. This works fine. What I want to do is not generate the same number as the immediate previous number.
function showRandomDotIcon() {
var randomDot = Math.floor(Math.random() * 13) + 1;
console.log(randomDot);
}
setTimeout(showRandomDotIcon, 3500);
So something like:
if(randomDot == previousDot) {
// skip to next number
}
You could take a closure over the last random value and check against it.
function showRandomDotIcon() {
var last;
return function () {
var randomDot;
do {
randomDot = Math.floor(Math.random() * 13) + 1;
} while (last === randomDot)
last = randomDot;
console.log(randomDot);
};
}
setInterval(showRandomDotIcon(), 1000);

JS Function Calls from HTML + Reactive display

The game is WAR, or Get Your Neighbour, a traditional game utilising a standard deck of 52 cards, no jokers. Currently the code recognises when a card is above 10 and so the rules of the game are being followed, all that is great, I've designed a timer that takes the value of the card 2-14, subtracts 10, then uses that number for the round of turns the other player has to draw above 10 before you win. Still building the cooperative/multiplayer element but for now, I'd just like to get this bloody button working!
When I click it, it does nothing. Before, it would tell me that "'timerf' is not a function". I'm probably doing something very obvious like problems with the order that things are loaded/data is parsed, but I'm still learning so I'd appreciate any help! Any questions, let me know.
var card = null; // setem 160517
var timer = null; //
window.onload = function() {
function draw(min, max) { // draw a card between 2-14
card = document.getElementById("draw").innerHTML = Math.floor(Math.random()*((max - min)+1) + min); // min 2, max 14
if (card > 10) {
timer = card - 10;
timerf(timer);
} else if (card < 11 && timer > 0) {
timer = timerf(timer-1);
}
} // draw
//draw(2,14);
document.getElementById("clickMe").onclick = draw(2,14);
} // window.onload
function timerf(timer) { // print turns to win
if (timer > 0 && timer < 5 && timer != 1) { // print turns to win
console.log("you have " + timer + " turns to win!");
} else if (timer == 1) {
console.log("you have " + timer + " turn to win!");
}
}
<div id="draw"></div>
<button id="clickMe">WAR!</button>
The return value of the draw function is undefined because it has no return statement.
document.getElementById("clickMe").onclick = draw(2,14);
… so you are assigning undefined to the onclick property.
You have to assign the function you want to call.

Categories