I am practically new to React. In this App I am using Hooks!
I've made a Countdown Timer that will show in a few seconds after logging in. I am unable to make it stop on a button click. I need some advise on this as I've been struggling for the past 2 days with this.
This is my code so far: (Please Help)
function Admin() {
const [isTimerOpen, setTimmer] = useState(false);
let history = useHistory();
// SET BY THE ADMIN
var minutesToCountDown = 0.9;
// TRANSFORM INTO SECONDS
var transformMinutesToSeconds = minutesToCountDown * 60
// KEEP A STATE
const [counterValue, setCounterValue] = useState(0);
const [isTimmerStoped, setStopTimer] = useState(false);
// FUNCTION TO HAPPEN EVERY 1 SECOND
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval)
}
}
// STARTS THE COUNTDOWN
var interval;
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
}
// ADD 0 IN FRONT ON THE TIME REMAINING
const addLeadingZeros = value => {
value = String(value);
while (value.length < 2) {
value = `0${value}`;
}
return value;
};
// CONVERT SECONDS INTO TIME REMAINING
function convertSeconds(seconds) {
var min = Math.floor(seconds / 60);
var sec = seconds % 60;
return addLeadingZeros(min) + ':' + addLeadingZeros(sec)
}
const logOutUser = () => {
logout();
return history.push(mainRoute)
}
function setTimer() {
const timer = setTimeout(() => {
setTimmer(true)
console.log('This will run after 3 seconds!')
startCountdown()
}, sessionTimeout);
return () => clearTimeout(timer);
}
useEffect(() => {
if (isTimmerStoped === false) {
console.log('Effect Starting', isTimmerStoped)
setTimer()
} else {
console.log('Effect Stopping', isTimmerStoped)
stopCountdown()
}
}, [isTimmerStoped, setStopTimer, minutesToCountDown]);
return <React.Fragment>
<CssBaseline />
<Container disableGutters maxWidth={false}>
<NavigationBar handleSignOut={logOutUser}/>
<TimerContent
timeRemaining={convertSeconds(counterValue)}
isTimerAlertOpen={isTimerOpen}
extendSessionBtn={stopCountdown}
logoutBtn={logOutUser}
clickOutsideButton={stopCountdown}/>
</Container>
</React.Fragment>
}
export default Admin;
You should do 2 things.
Make the interval variable a ref. This way it's value will be unique every where it imported. Note: Just creating a variable above the component is a bad idea because that variable will be shared between each component that imports the Admin component, which will lead to bugs.
Wrong
let interval;
function Admin() {
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Right
function Admin() {
const interval = React.useRef();
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Add clearInterval to your stopCountdown function. You can remove the clearInterval in the timeIt function and move it into stopCountdown. Please take a look at this codepen I made to demostrate
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
clearInterval(interval.current) // Clear the interval here
}
You shouldn't maintain the intervalId returned by setInterval in a functional variable since it will be reset on re-render and you will loose the actual timer Id. You must instead use useRef hook to store the timerId
const interval = useRef(null);
...
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval.current)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval.current)
}
}
...
// STARTS THE COUNTDOWN
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
Related
I have set 4 timeout for audios in my application and I need to stop the audios after user click. The macro function is working correctly, however the clearTimout does not stop the sound. Anyone knows how to clear it?
export function handlePlay(audio) {
audio.currentTime = 0;
return audio.play();
}
export function handleConversation(clear) {
const timer1 = () => setTimeout(() => {
handlePlay(conversation[Math.floor(Math.random() * conversation.length)]);
}, TIME1);
const timer2 = () => setTimeout(() => {
handlePlay(conversation[Math.floor(Math.random() * conversation.length)]);
}, TIME2);
const timer3 = () => setTimeout(() => {
handlePlay(conversation[Math.floor(Math.random() * conversation.length)]);
}, TIME3);
const timer4 = () => setTimeout(() => {
handlePlay(conversation[Math.floor(Math.random() * conversation.length)]);
}, TIME4);
if (clear) {
console.log('enter clear');
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
clearTimeout(timer3);
clearTimeout(timer4);
};
}
timer1();
timer2();
timer3();
timer4();
}
after clearTimeouts call this code
audio.pause();
audio.currentTime = 0;
Here a suggestion of what you could do.
I guess this could be improved further regarding how this handleConversation function is used, I didn't really get the whole idea and there is still some inconsistencies...
function createAudio(track) {
track.audio = conversation[Math.floor(Math.random() * conversation.length)];
}
export class Track {
constructor(time) {
this.time = time;
this.timeoutid = 0;
this.audio = new Audio();
}
timer() {
this.timeoutid = setTimeout(() => {
createAudio(this);
handlePlay(this.audio);
}, TIME1);
}
play() {
this.audio.currentTime = 0;
this.audio.play();
}
stop() {
this.audio.pause();
this.audio.currentTime = 0;
clearTimeout(this.timeoutid)
}
}
export function handleConversation(clear) {
const track1 = new Track(TIME1);
const track2 = new Track(TIME2);
const track3 = new Track(TIME3);
const track4 = new Track(TIME4);
// this part actually doesn't make a lot of sense, since all tracks will be recreated each time the function is called.
// I don't really understand how this is used.
// I imagine the tracks should more likey be stored outside the function in a persistent object.
// I will modify my answer if you provide more details about how you use handleConversation
if (clear) {
console.log('enter clear');
return () => {
[track1, track2, track3, track4].forEach(track => {
track.stop()
});
};
}
[track1, track2, track3, track4].forEach(track => {
track.timer()
});
}
I implemented a timer which works fine but the only problem is that it doesn't clearInterval when the timer ends .
My code looks like this :
const [currentCount, setCurrentCount] = React.useState(10);
const [intervalId, setIntervalId] = React.useState(10);
React.useEffect(() => {
const intervalId = setInterval(timer, 1000);
// store intervalId in the state so it can be accessed later:
setIntervalId(intervalId);
return () => {
clearInterval(intervalId);
};
}, []);
const timer = () => {
setCurrentCount((prev) => {
if (prev > 0) {
return prev - 1;
} else if (prev === 0) {
console.log("Still Runs");
clearInterval(intervalId);
return prev;
}
});
};
The value of currentCount decreases from 10 to 0 but I want to clear the interval when it reaches 0 and I tried to clear it with this piece of code clearInterval(intervalId) but didn't work .
How can I clearInterval when the value of currentCount reaches 0 ?
Your useEffect hook is missing an important dependency from its dependency array: timer. To make things simpler on yourself, you can move the timer variable inside the effect (unless you have a good reason for it to be outside). This ends up having the added bonus that the timer function will be able to reference the interval ID without maintaining it in state.
const [currentCount, setCurrentCount] = React.useState(10);
React.useEffect(() => {
const timer = () => {
setCurrentCount((prev) => {
if (prev > 0) {
return prev - 1;
} else if (prev === 0) {
console.log("Still Runs");
clearInterval(intervalId);
return prev;
}
});
};
const intervalId = setInterval(timer, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
I created a timer function it's working but when I click clearInterval it's not working, timer still going on.
Here is my function to start timer. Maximum limit for timer is 60sec
const StartRecord = ()=>{
const timeout = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
}
}, 1000);
console.log(timeout);
if (time == 60) {
clearInterval(timeout);
}
}
here is my function to stop timer
const onStopRecord = () => {
clearInterval(time);
}
can anyone tell me why it's not working?
You are trying clearInterval using time. It should be timeout
const onStopRecord = () => {
clearInterval(timeout);
}
you can use useEffect to do this task
useEffect(() => {
let interval
if(time != 60){
interval = setInterval(() => setTime((prev) => prev + 1), 1000);
}
return () => clearInterval(interval);
}, [time])
Hello mighty people from internet,
I am trying to build a countdown timer app with pause and reset buttons using React hooks.
The timer countdown should stop once pause or reset button gets clicked performed by function pauseCountDown() and reset(), respectively.
However, the timer does not stop after pauseCountDown() or reset() executed.
I don’t quite understand why setInterval() does not stop after clearInterval() gets executed.
clearInterval() should be executed when isPaused === true.
However, the value of “isPaused” instantly switches back to false along with the next iteration of nonstopped setInterval().
Any ideas or thoughts what I missed or mistakes I have?
Thank you.
Here is the code of the child component performing timer countdown:
function TimerPanel(props) {
var launch;
var secLeft = parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec)
const startCountDown = () => {
props.setIsPaused(false)
launch = setInterval(decrement, 1000)
}
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60)
var secCountDown = Math.floor((secLeft % 60) * 100) / 100
props.setTimerNow({...props.timerNow, min: minCountDown, sec: secCountDown})
}
}
const pauseCountDown = () => {
props.setIsPaused(true)
clearInterval(launch)
}
const reset = () => {
props.setIsPaused(false)
clearInterval(launch)
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({...props.timerNow, min: props.initialTimer.min, sec: props.initialTimer.sec})
}
Since launch is just an ordinary variable, its value will not survive across rerenderings of TimerPanel. You'll need to store it inside a ref with useRef
// add useRef to your imports from react
import { useRef } from "react";
function TimerPanel(props) {
const launch = useRef();
var secLeft =
parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec);
const startCountDown = () => {
props.setIsPaused(false);
clearInterval(launch.current) // clean up any old timers
launch.current = setInterval(decrement, 1000);
};
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60);
var secCountDown = Math.floor((secLeft % 60) * 100) / 100;
props.setTimerNow({
...props.timerNow,
min: minCountDown,
sec: secCountDown,
});
}
}
const pauseCountDown = () => {
props.setIsPaused(true);
clearInterval(launch.current);
};
const reset = () => {
props.setIsPaused(false);
clearInterval(launch.current);
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({
...props.timerNow,
min: props.initialTimer.min,
sec: props.initialTimer.sec,
});
};
// ...
}
I am working on knockout js.
In that i have a recursive function which executes a function every minute. for that am using a timer every 60 sec it will execute also same will be reflecting in the UI also.
In my case, if i try to assign or initialize a timer value(observable) which is inside a loop, it doesn't reflecting instead of reflecting it is added to the pipeline and that much time loop is running simultaneously.
In that case i want to kill the loop and again want to restart every time i am changing the timer value.
timerInSec=60;
var loop = function () {
if (this.timer() < 1) {
myFunction()
this.timer(this.timerInSec - 1);
setTimeout(loop, 1000);
} else {
this.timer(this.timer() - 1);
setTimeout(loop, 1000);
}
};
loop();
Here is my solution. Please check.
timerInSec = 60;
const Loop = (function () {
let timer = 0;
let timerId = -1;
const myFunction = function () {
console.log('finished');
}
const fnLog = function (tm) {
console.log('current time = ', tm);
}
const fnProc = function () {
timerId = setTimeout(myFunction, 1000 * timer);
}
return {
start: function (tm = 60) {
this.stop();
timer = tm;
fnProc();
},
stop: function () {
if (timerId !== -1) {
clearTimeout(timerId);
timerId = -1;
}
}
}
})();
Loop.start(timerInSec);
setTimeout(() => {
Loop.start(timerInSec);
}, 500);