Show countdown when holding button then show alert - javascript

I'm trying to create a button, which on hold shows a countdown of 3 seconds, if kept on hold for 3 seconds it shows an alert, but for some reason the countdown doesn't reset properly AND the alert fires up anyway, at every click (after the timeout)
my code is:
const [showCountdown, setShowCountdown] = useState(false)
const [holdTimer, setHoldTimer] = useState(3)
var timer = 0, interval;
const refreshDown = () => {
setShowCountdown(true)
interval = setInterval(function(){
setHoldTimer((t) => t - 1)
},1000);
timer = setTimeout(function(){
if (confirm('Refresh page?')){
window.location.reload()
}
},3000)
}
const refreshUp = () => {
clearTimeout(timer)
clearInterval(interval)
setShowCountdown(false)
setHoldTimer(3)
}
my html button has these two:
<svg onMouseDown={() => refreshDown()} onMouseUp={() => refreshUp()}>
...
</svg>

Have you tried with useRef ?
const timer = useRef();
const interval = useRef();
const refreshDown = () => {
setShowCountdown(true);
interval.current = setInterval(function () {
setHoldTimer((t) => t - 1);
}, 1000);
timer.current = setTimeout(function () {
if (confirm("Refresh page?")) {
window.location.reload();
}
}, 3000);
};
const refreshUp = () => {
clearTimeout(timer.current);
clearInterval(interval.current);
setShowCountdown(false);
setHoldTimer(3);
};

React component is rerendered each time state or props are changed. When setHoldTimer is executed, it changes the state. It causes reexecuttion of component’s code, so local variables “timer” and “interval” are declared again with values “0” and “undefined”, also new “refreshUp” function is created referencing new “timer” and “interval”. When you release the mouse, new “refreshUp” is called, interval and timeout are not cleared.
Try to define timer and interval as a state or with “useRef”. This way they will not be redefined during rerender.

Related

ReactNative: changing state inside setInterval/useEffect not working?

I'm trying to make a tinder-like clone where I want to show each 5 seconds a different user photo, I first get array of only images from matches and then with setInterval which I finally got to work inside effect I loop through images array and change active index of image, however when I console log active image, it's always the same image...
This always returns same active Index and same active image
console.log(images[activeImageIndex]);
useEffect(() => {
getImagesArray(mapStore.blurredMatches);
const interval = setInterval(() => {
if(images.length>0)
{
flipAndNextImage();
}
}, 5000);
return () => clearInterval(interval);
}, []);
const getImagesArray = (users) =>
{
let copy = [];
for(var i=0;i<users.length;i++)
{
copy.push(users[i].images[0]);
}
setImages(copy);
console.log('Setting images array',images);
};
const flipAndNextImage= () =>
{
console.log(images,'images');
if(activeImageIndex == images.length-1)
{
setActiveImageIndex(0);
console.log(images[activeImageIndex]);
}
else
{
setActiveImageIndex(activeImageIndex+1);
console.log(images[activeImageIndex]);
}
};
I believe this is because you update your activeImageIndex before logging the state

now.getTime() inside useEffect doesn't work

I wanted to record how many time passed until user click the button.
My plan was like: (the time when user enter the page) - (the time when user click the button) = how many milliseoconds passed!
but my code had an bug and error. my useEffect didn't go what I wanted..
It didn't memorized the time when user entered the page.
console said :
👉undefined
and when first hit:
-1611798597788milliseconds passed
(it worked well as I planned when second hit. weird 🤔)
here's my code:
const UserTimer = () => {
const [startTime, setStartTime] = useState(null);
const [endTime, setEndTime] = useState(0);
const [diff, setDiff] = useState(0);
useEffect(() => {
let now = new Date();
setStartTime(now.getTime());
console.log('👉' + startTime);
}, []);
const onClick = () => {
setEndTime(Date.now());
setDiff(endTime - startTime);
setStartTime(endTime);
};
return (
<div>
<p>{diff}milliseconds passed</p>
<button onClick={onClick}>click here</button>
</div>
);
};
thank you in advance.
setState does not happen synchronously, so you will not see it change instantly. Best to set a variable then use that variable for your math.
Maybe something like this?
const UserTimer = () => {
const [startTime, setStartTime] = React.useState(null);
const [diff, setDiff] = React.useState(0);
React.useEffect(() => {
const now = Date.now()
setStartTime(Date.now());
console.log('👉' + now);
}, []);
const onClick = () => {
const endTime = Date.now()
setDiff(endTime - startTime);
setStartTime(endTime);
};
return (
<div>
<p>{diff}milliseconds passed</p>
<button onClick={onClick}>click here</button>
</div>
);
};
That's because your startTime state is set as null here:
const [startTime, setStartTime] = useState(null);
So first click it is still null, so your setDiff(endTime - startTime); returns the negative number. You can try to solve it using:
useEffect(() => {
let now = new Date();
setStartTime(now.getTime());
console.log('👉' + startTime);
}, [startTime]);

Pause and restart setTimeout in loop

I have put a delay in my loop by awaiting a function that returns a Promise with setTimeout(). I am able to start and reset the loop but would also like to be able to pause the loop somehow. After clicking the start button again, I would like to continue looping from the last looped element in the array.
$(document).ready(() => {
$('#btn-start').click(() => {
loopMoves();
});
$('#btn-pause').click(() => {
// ...
});
$('#btn-reset').click(() => {
clearTimeout(timer);
});
})
var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
async function loopMoves() {
for(let i = 0; i < moves.length; i++){
console.log(moves[i]);
await delay(2000);
}
}
var timer;
function delay(ms) {
return new Promise((x) => {
timer = setTimeout(x, ms);
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn-start">start</button>
<button id="btn-pause">pause</button>
<button id="btn-reset">reset</button>
Instead of using for loop this could be achieved by using setInterval() and a generator function.
Please refer below snippet for the working example:
$(document).ready(() => {
let loop = loopMoves();
$('#btn-start').click(() => {
console.log("****STARTED*****");
loop("PLAY");
});
$('#btn-pause').click(() => {
console.log("****PAUSED*****");
loop("PAUSE");
});
$('#btn-reset').click(() => {
console.log("****RESET*****");
loop("RESET");
});
})
const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
function loopMoves() {
let moves = generateMoves();
let intervalId;
function startInterval(){
intervalId = setInterval(()=>{
const move = moves.next();
if(move.done)
clearInterval(intervalId);
else
console.log(move.value);
}, 2000);
}
return (state) => {
switch(state){
case "PLAY": startInterval(); break;
case "PAUSE": clearInterval(intervalId); break;
case "RESET": moves = generateMoves();
clearInterval(intervalId); break;
}
}
}
function* generateMoves(){
for(let i = 0; i < moves.length; i++){
yield moves[i];
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn-start">start</button>
<button id="btn-pause">pause</button>
<button id="btn-reset">reset</button>
You might consider flipping things around into more of a time based "state machine".
When play is clicked, you start your timmer ("playing" state), at the end of the timmer you check the current state. If the state is still "playing" you grab the next move and display that and start the timmer again. If no interaction happens this repeats until all moves are done and the state goes to "stopped".
If someone clicks pause you go to "paused" state but leave the rest as is. When play is clicked again (going from "paused" to "playing") you just pick up where you were. When stop is clicked you reset everything. When play is clicked from the "stopped" state you set the move index to 0 and do the first move, start the timmer.
This does mean each update only happens every 2 seconds but that might be acceptable and it's a whole lot easier than the alternative of pausing the timmer etc.
const moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4']; // These can be loaded however
let state = "stopped"; // The starting state
let currentMoveIndex = 0; // Used to track which move to grab for the next tick
$(document).ready(() => {
$('#btn-start').click(() => {
state = "playing";
tick();
});
$('#btn-pause').click(() => {
state = "paused";
});
$('#btn-reset').click(() => {
state = "stopped";
tick(); // Since we put the clean up inside the tick function we run it again to be sure it's executed in case it was paused
});
})
function tick() {
switch(state) {
case "playing":
if (currentMoveIndex + 1 === moves.length) {
break;
}
const move = moves[currentMoveIndex];
console.log(move); // Or whatever you wish to do with the move
currentMoveIndex += 1;
setTimeout(tick, 2000);
break;
case "paused":
// Do whatever you want based on entering the paused state,
// Maybe show some message saying "Paused"?
break;
case "stopped":
currentMoveIndex = 0;
break;
}
}
If you're using something like React or Angular you can wrap this into a Component/Controller to keep things together, or wrap it all into a Game function if you're using plain JavaScript
I have tried out these answers and both of them seem to work, thank you. I return to this question because I previously thought of something and would like to get some feedback on it. What if I would store the looped items in a new array and restart the loop from the position of the last stored item setting i = done.length (done being the array of looped items). When clicking the pause button clearTimeout() is called and when clicking the reset button, I clear out the done array after calling clearTimeout(), start still runs the function containing the loop.
I understand that I am not really pausing the timer but rather stopping it and restarting it from the last position in the array. Would this be considered 'bad practice' or could this also be an acceptable solution?
$(document).ready(() => {
$('#btn-start').click(() => {
loopMoves();
});
$('#btn-pause').click(() => {
clearTimeout(timer);
});
$('#btn-reset').click(() => {
clearTimeout(timer);
done = [];
});
})
var moves = ['Nf3', 'd5', 'g3', 'g6', 'c4', 'dxc4'];
var done = [];
async function loopMoves() {
for(let i = done.length; i < moves.length; i++){
console.log(moves[i]);
done.push(moves[i]);
await delay(2000);
}
}
var timer;
function delay(ms) {
return new Promise((x) => {
timer = setTimeout(x, ms);
});
}

What am I doing bad with localStorage?

I am doing a time-to-click score table but first I have to localstorage each time I get in a game but I dont know how I have been trying everything but it still not working, I need to finish this fast, and I need help... otherwise I would try everyday to resolve this alone because I know that's the way to learn.. When I press the finished button It says that times.push() is not a function.
let times = Array.from(
{ length: 3 }
)
let interval2;
// Timer CountUp
const timerCountUp = () => {
let times = 0;
let current = times;
interval2 = setInterval(() => {
times = current++
saveTimes(times)
return times
},1000);
}
// Saves the times to localStorage
const saveTimes = (times) => {
localStorage.setItem('times', JSON.stringify(times))
}
// Read existing notes from localStorage
const getSavedNotes = () => {
const timesJSON = localStorage.getItem('times')
try {
return timesJSON ? JSON.parse(timesJSON) : []
} catch (e) {
return []
}
}
//Button which starts the countUp
start.addEventListener('click', () => {
timerCountUp();
})
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
console.log('click');
times = getSavedNotes()
times.push({
score: interval2
})
if (interval) {
clearInterval(interval);
clearInterval(interval2);
}
})
You probably need to change the line times = JSON.parse(localStorage.times || []) to times = JSON.parse(localStorage.times) || [] this makes sure that if the parsing fails it will still be an array.
Even better is wrap them with try-catch just in case if your JSON string is corrupted and check if the time is an array in the finally if not set it to empty array.

Timer: Play very few Minutes a warnsignal for a duration

I want to build a "timer". User can define a duration and the interval. So it will start with a warnsignat, then there will be a warnsignal, which peeps every interval and at the end there should be one.
The problem is: it doesn't work.
The audio just plays all the time.
Possible errorsources:
peep.loop = false does not work
it does not block.
const peepUrl = require('./../../../Assets/peep.mp3');
const peep = new Audio(peepUrl);
peep.loop = false;
_peep = () => {
peep.play();
if(this.state.myInterval > 0) {
setTimeout(() => {
peep.play();
}, 60000*this.state.myInterval);
}
clearInterval(this.state.myDuration)
peep.play();
}

Categories

Resources