I'm working on React pomodoro clock and I got stuck on joining if/else with setTimeout(). Here is my code:
React.useEffect(() => {
if(props.isOn === true) {
if(props.timeLeft <= 0) {
setTimeout(function() {
if(props.activeClockType === 'Session') {
props.handleClockType('Break');
props.handleTimeLeft(props.breakLength * 60);
} else {
props.handleClockType('Session');
props.handleTimeLeft(props.sessionLength * 60);
}
}, 1000);
}
const id = setInterval(count, 1000);
return () => clearInterval(id);
}
});
When the clock goes to 00:00 it's stopping for 1sec and then showing Break and timer sets to breakLength. Great, but after 1sec it's not counting down but changing back to Session and counting sessionLength.
When I delete setTimeout (commented in CodePen) it works good but it's not stopping at 00:00 for 1sec but immediately changes on Break what looks ugly.
How to make it stop on 00:00 for 1 second before counting other activeClockType?
I've spent whole evening on looking for any clue but still I don't have any idea what is wrong. I tried to put setTimeout in every possible combination with if but effect is always the same. From this topic I suspect that I should probably use clearTimeout but also tried it in any combination and got nothing.
Or maybe I should try another possibility to run countdown than useEffect()? Earlier I made working timer in class component (commented in CodePen) with componentWillReceiveProps but it's deprecated and my attempts to change it on other functions gave nothing. I don't fully understand this Effect Hook but it was the only way I found that is counting well. And it's shorter than class component.
Here is my full code on CodePen
Thank you in advance.
I think there is two problem here:
1) Every time you use the hook to set the state, you fired the React.useEffect, if you set the state twice, the React.useEffect would be fired twice.
2) use setTimeout to replace setInterval to avoid update state too many times.
Problems are showing below:
React.useEffect(() => {
if(props.isOn === true) {
if(props.timeLeft <= 0 && !changeClockType) {
setTimeout(function() {
if(props.activeClockType === 'Session') {
props.handleClockType('Break'); // ==> Fired update and then excuted React.useEffect
props.handleTimeLeft(props.breakLength * 60); // ==> Fired update again and then excuted React.useEffect again
} else {
props.handleClockType('Session');
props.handleTimeLeft(props.sessionLength * 60);
}
}, 1000);
}
const id = setTimeout(count, 1000); // ==> modify setInterval to setTimeout
return () => clearInterval(id);
}
});
I try to modify the code like this:
let changeClockType = false; // ==> Should be put here because it couldn't be put in the function component
function Timer(props) {
const count = () => props.handleTimeLeft(props.timeLeft - 1);
function startStop() {
props.handleIsOn(props.isOn === false ? true : false);
}
React.useEffect(() => {
console.log("step 1");
if(props.isOn === true) { console.log("step 2");
if(props.timeLeft <= 0 && !changeClockType) { console.log("step 3");
setTimeout(function() {
if(props.activeClockType === 'Session') { console.log("step 4");
changeClockType = true;
props.handleClockType('Break'); // ==> Fired update and then excuted React.useEffect
console.log("Change to Break")
props.handleTimeLeft(props.breakLength * 60); // ==> Fired update again and then excuted React.useEffect again
changeClockType = false;
console.log("Set breakLength")
} else { console.log("step 5");
changeClockType = true;
props.handleClockType('Session');
props.handleTimeLeft(props.sessionLength * 60);
changeClockType = false;
}
}, 1000);
}
const id = setTimeout(count, 1000); // ==> modify setInterval to setTimeout
return () => clearInterval(id);
}
});
The code isn't so clean but can be run well.
Related
I wrote a code for timer of 60 seconds but setTimeout() is executing properly for few minutes but after some time it is resolving the function but without delay and the function is getting resolved in each second.
Here, this.rateCardTimerText is init with zero in the first call.
Please provide any proper solution if available or is there any alternative way to achieve it then please also mention that.
onChangeTimerChangeInfinity() {
let no = Number(this.rateCardTimerText)
if (no >= 100) {
no = 0
this.getCurrencyList()
}
else {
no += 1.66
}
this.rateCardTimerText = no
this.forceUpdate()
setTimeout(this.onChangeTimerChangeInfinity.bind(this), 1000)
}
Use setInterval() instead.
setInterval( () => onChangeTimerChangeInfinity, 1000);
onChangeTimerChangeInfinity() {
let no = Number(this.rateCardTimerText);
if (no >= 100) {
no = 0;
this.getCurrencyList();
}
else {
no += 1.66;
}
this.rateCardTimerText = no;
this.forceUpdate();
}
Make sure you clear out the previous setTimeout each time you run it, like so:
let intervalObj;
onChangeTimerChangeInfinity() {
clearTimeout(intervalObj)
let no = Number(this.rateCardTimerText)
if (no >= 100) {
no = 0
this.getCurrencyList()
}
else {
no += 1.66
}
this.rateCardTimerText = no
this.forceUpdate()
intervalObj = setTimeout(this.onChangeTimerChangeInfinity.bind(this), 1000)
}
Also, just as with the above setInterval() option, you may end up with code tripping over itself because you're forcing this thing to fire every second regardless. If getCurrencyList() or forceUpdate() are async, consider rewriting a second function as a promise, then something like:
let intervalObj;
onChangeTimerChangeInfinity() {
clearTimeout(intervalObj)
doAsyncStuffInPromise().then( () => {
intervalObj = setTimeout(this.onChangeTimerChangeInfinity.bind(this), 1000)
})
}
I am using a React hook to build a countdown timer and I want to use a setInterval() to decrease the time but at first it does the correct if statement (the last one) but then it keeps doing it even tough it's wrong. I tried console logging "seconds" and "minutes" and the browser keeps saying 25 and 0 (as I set them initially) even tough when I press the start button I can see the minutes showing another number.
the full code : https://pastebin.com/bHyuLGn2
The code which I say is relevant:
const [timeleft, setTimeLeft] = useState({
minutes: 25,
seconds: 0,
});
const {
minutes,
seconds
} = timeleft;
function handleStart() {
setStartButton(!startButton);
setInterval(() => {
if (seconds > 0) {
setTimeLeft((prevValue) => {
return {
...prevValue,
seconds: prevValue.seconds - 1,
};
});
} else if (seconds === 0) {
if (minutes === 0) {
clearInterval();
} else {
setTimeLeft((prevValue) => {
return {
minutes: prevValue.minutes - 1,
seconds: 59,
};
});
}
}
}, 1000);
}
No, setInterval is doing the correct thing actually, but your code is probably not doing what you want it to do.
Inside your interval, you are calling setTimeLeft. setTimeLeft causes the component to re-render, and your setInterval to reset every single time.
If you want to do what you're trying to do, you're likely going to have to pull that state out into its own hook, or architect this differently.
You using setInterval wrong, because it should be like this:
let myInterval = setInterval(() => {/* bla-bla-bla */}, 1000)
// ...
clearInterval(myInterval)
but in you code I see this:
setInterval(() => {/* bla-bla-bla */}, 1000)
// ...
clearInterval()
so, clear WHAT interval?
The problem with setInterval in react is that usually it makes the component render again, and the new render will call a new setInterval.
Here's an interesting article that provides a solution:
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
I'm implementing count down
and use useRef hook to using it when clean setTimeout when the user navigates to the next screen to avoid cancel all subscription warning and it's work!
But I have something weird when count - 1 i can see "hey" in the console! although not cleaning the setTimeOut!!
I don't want to clean it in this case but why should loggin every time count changes!
code snippet
const [seconds, setSeconds] = useState(40);
const countRef = useRef(seconds);
useEffect(() => {
if (seconds > 0) {
countRef.current = setTimeout(() => {
setSeconds(seconds - 1);
}, 1000);
} else {
setSeconds(0);
}
return () => {
console.log('hey'); // every count down it's appeared
clearTimeout(countRef.current);
};
}, [seconds]);
You see "hey" because you're using seconds as a dependency. So every time seconds changes, the effect must run again leading to the effect's destroy function (the function you returned from the effect) to be invoked.
Instead of having seconds as a dependency, you should instead have setSeconds.
const [seconds, setSeconds] = React.useState(10);
useEffect(() => {
let didUnsub = false;
const id = setInterval(() => {
setSeconds((prev) => {
// while the count is greater than 0, continue to countdown
if (prev > 0) {
return prev - 1;
}
// once count eq 0, stop counting down
clearInterval(id);
didUnsub = true;
return 0;
});
}, 1000);
return () => {
console.log("unmounting");
// if the count didn't unsubscribe by reaching 0, clear the interval
if (!didUnsub) {
console.log("unsubscribing");
clearInterval(id);
}
};
}, [setSeconds]);
If you look at the example below, you'll see that the effect is only run once, when the component is mounted. If you were to cause the component to dismount, the destroy function would be invoked. This is because the setState is a dispatch function and doesn't change between renders, therefor it doesn't cause the effect to continuously be called.
In the example you can click the button to toggle between mounting and dismounting the counter. When you dismount it notice that it logs in the console.
Example: https://codesandbox.io/s/gallant-silence-ui0pv?file=/src/Countdown.js
I'm trying to create a timer with the code below, but when I console log time in the tick function all I get back is 60 continuously , if I pass the console.log after time - 1 I get NaN , which I guess means that time for some reason is not processed as an integer.
I cannot use state as using state for this timer re renders my components again and again which makes everything go crazy.
componentWillMount() {
var time = 60;
this.interval = setInterval(this.tick.bind(this, time), 1000);
}
tick(time) {
time = Number.parseInt(time) // This does nothing for some reason
console.log(time);
time = time - 1;
if (time <= 0) {
console.log('Hi');
} return time;
}
EDIT: I found your actual problem.
You need to store time in state. Add it to your initial state as 60, change the interval line to:
setInterval(this.tick, 1000)
and your tick function to:
tick = () => {
this.setState((prevState) => {
return {
time: prevState.time - 1
}
}, () => {
if(this.state.time <= 0){
console.log('hi')
}
})
}
This way, time is tracked outside of your function scope and the value should always be correct. By passing an anonymous callback as the second argument to setState you ensure that the check only happens after time is decremented. The one issue I'd see with this is that React can be a bit funky with running setState and may not run every second, so this could end up being slightly imprecise.
componentWillMount() {
var time = 60;
this.interval = setInterval(
(function() {
time--;
this.tick(time);
}).bind(this),
1000);
}
tick(time) {
time = Number.parseInt(time) // This does nothing for some reason
console.log(time);
//time = time - 1; //I removed this line
if (time <= 0) {
console.log('Hi');
}
return time;
}
componentWillMount would look a lot prettier if you used arrow functions
componentWillMount() {
let time = 60;
this.interval = setInterval(() => {
time--;
this.tick(time);
},
1000);
}
Now you are changing time in the right place and sending it on to the function on each call.
I want to execute a block of code every two seconds—to accomplish this, I thought the easiest way would be to retrieve the current system time as so:
if ((Date().getSeconds()) % 2 == 0) {
alert("hello"); //I want to add code here!
}
However, my alert is not printing to the screen every two seconds. How can this be properly implemented?
In order to run a block of code every x seconds, you can use setInterval.
Here's an example:
setInterval(function(){
alert("Hello");
}, x000); // x * 1000 (in milliseconds)
Here's a working snippet:
setInterval(function() {
console.log("Hello");
}, 2000);
You can use setInterval(). This will loop every 2 seconds.
setInterval(function() {
//something juicy
}, 2000);
Try using the setInterval() method
setInterval(function () {console.log('hello'); }, 2000)
This should work for you.
setInterval(function() {
//do your stuff
}, 2000)
However, to answer why your code is not working, because it is not in a loop.
runInterval(runYourCodeHere, 2);
function runInterval(callback, interval) {
var cached = new Array(60);
while (true) {
var sec = new Date().getSeconds();
if (sec === 0 && cached[0]) {
cached = new Array(60);
}
if (!cached[sec] && sec % interval === 0) {
cached[sec] = true;
callback();
}
}
}
function runYourCodeHere() {
console.log('test');
}