I'm creating a countdown timer and I need to call clearInterval from a different function as I want to start and pause the countdown with two different buttons
Here's a piece of my code
const startCountdown = () => {
const countdown = setInterval(() => {
setSecondsElapsed(secondsElapsed => secondsElapsed - 1);
}, 1000);
};
const pauseCountdown = () => {
clearInterval(countdown);
};
The countdown starts when I press the initial button but it doesn't pause when I press the button calling pauseCountdown()
Use a React ref to hold the timer reference. When setting the interval store the countdown reference as the ref's current value, and in the other function clear the interval using the ref's current value.
const countDownRef = React.useRef();
const startCountdown = () => {
countDownRef.current = setInterval(() => {
setSecondsElapsed(secondsElapsed => secondsElapsed - 1);
}, 1000);
};
const pauseCountdown = () => {
clearInterval(countDownRef.current);
};
try declaring countdown globally so it can be accessed from any function. I'd also recommend using var instead of const for things that will be frequently redefined, such as a pausable countdown loop.
try this:
var countdown;
const startCountdown = () => {
countdown = setInterval(() => {
setSecondsElapsed(secondsElapsed => secondsElapsed - 1);
}, 1000);
};
const pauseCountdown = () => {
clearInterval(countdown);
};
The countdown value is inside the scope of startCountdown function hence it can not be accessed from pauseCountdown function which is not inside that very scope.
There are many different ways to do this job properly. I would advise you to be more structural with the help of new abstractions.
I may perhaps use a Countdown class for this job.
class Countdown {
#sid;
#start;
constructor(start){
this.#start = start;
}
startCountdown(){
this.#sid = setInterval( _ => ( !this.#start && clearInterval(this.#sid)
, console.log(this.#start--)
)
, 1000
);
}
pauseCountdown(){
clearInterval(this.#sid);
}
}
var cd = new Countdown(10);
cd.startCountdown();
setTimeout(_ => cd.pauseCountdown(), 5001)
setTimeout(_ => cd.startCountdown(), 10000)
The private class fields #sid and #start keep the clearInterval id and starting value respectively. The thing is they are not exposed to the outer world.
Related
newbie to Javascript & React, trying to learn my way through React by building small projects. I'm working on a countdown timer right now, and I'm wondering if there's a better way of handling the pause/stop logic.
Right now, I'm calling setInterval() when my component is no longer in a "Paused" !paused state, and returning a cleanup() function to clear the interval. When I console logged it, it only cleared interval on pause/start.
However, I want to add functionality to automatically stop the timer once it hits zero, which requires adding duration state as a second dependency. When I logged this to console, I realized that my setDuration and clearInterval was now running at every interval, rather than only on the pause/start (it looks like it's calling setInterval() at every "tick" now.
My question is - is this a problem? And is there a better way to do this?
function Timer(props) {
const [duration, setDuration] = useState(10);
const [paused, setPaused] = useState(true);
useEffect(() => {
let timerId;
if (!paused) {
timerId = setInterval(() => {
setDuration(prev => prev - 1);
}, 1000);
// console.log(timerId);
}
if (duration === 0) {
// console.log(`Time's up`);
clearInterval(timerId);
}
return function cleanup() {
// console.log(`Clearing ${timerId}`);
clearInterval(timerId);
};
}, [paused, duration]);
const handleClick = (e) => {
!paused ? setPaused(true) : setPaused(false);
};
return (
<>
<h3>{duration}</h3>
<Button paused={paused} onClick={handleClick} />
</>
);
}
In your code, duration is changing on every interval and because it is passed as a parameter in dependecy array, it is triggering useEffect on each interval which is causing it to the setDuration and clearInterval to run at each interval.
you can use ternary operator to check if the duration is less than zero then you can automatically set the duration to zero by using setDuration(0)
function Timer(props) {
const [duration, setDuration] = useState(10);
const [paused, setPaused] = useState(true);
useEffect(() => {
let timerId;
if (!paused) {
timerId = setInterval(() => {
setDuration((prev) => prev - 1);
}, 1000);
console.log(timerId);
}
return function cleanup() {
console.log(`Clearing ${timerId}`);
clearInterval(timerId);
};
}, [paused]);
const handleClick = (e) => {
!paused ? setPaused(true) : setPaused(false);
};
return (
<>
{duration >= 0 ? <h3>{duration}</h3> : setDuration(0)}
<button paused={paused} onClick={handleClick} />
</>
);
}
Play around with the code here
So I'm making a timer in html and javascript and I am displaying it on a button. I want to make it so that when the value of time is equal to zero, it alerts the user. Here is my code so far. btw, the code is part of the timer code.
const time2=
document.getElementById("countdown")
if(time2==0:00)
alert("You have ran out of time, better luck next time!")
You can create a timer with a setInterval() and trigger some functionality when a value is reached, in this case the timer is cleared, i hope this helps:
const createTimer = (num) => {
const timer = setInterval(() => {
console.log(num)
num--;
if(num == 0) {
console.log("You have ran out of time, better luck next time!")
clearInterval(timer)
}
}, 1000)
}
createTimer(4)
something like this?
let div = document.getElementById('count')
let countdown = 10
let stopper = setInterval(myTimer, 1000)
function myTimer() {
countdown--
div.innerHTML = countdown + " seconds left"
if (countdown == 0) {
clearInterval(stopper)
}
}
<div id='count'>10 seconds left</div>
Here is one possible implementation of a very simple Timer using a class. Most important is setInterval() which will allow you to run a function every x milliseconds.
This timer uses a callback function onUpdate(seconds) which is called every time the timer is updated and provides the remaining seconds as a parameter. You could certainly extend on that.
It is also required to wait for the HMTL document to have loaded before trying to access the DOM using the DOMContentLoaded event.
class Timer {
#interval;
#initial;
#seconds;
constructor(seconds, onUpdate) {
this.#initial = seconds;
this.#seconds = seconds;
this.onUpdate = onUpdate;
this.onUpdate(seconds);
}
start() {
this.#interval = setInterval(() => {
this.#seconds--;
if (this.#seconds === 0) this.stop();
this.onUpdate(this.#seconds);
}, 1000);
}
reset() {
this.stop();
this.#seconds = this.#initial;
this.onUpdate(this.#seconds);
}
stop() {
clearInterval(this.#interval);
this.onUpdate(this.#seconds);
}
}
// wait for HTML document to load
window.addEventListener("DOMContentLoaded", e => {
const startBtn = document.getElementById("start");
const stopBtn = document.getElementById("stop");
const resetBtn = document.getElementById("reset");
// create a timer and assign it seconds as well as a callback function that is called every second
const timerDiv = document.getElementById("countdown");
const timer = new Timer(5, (seconds) => {
// called every seconds with seconds to go as an argument
timerDiv.textContent = `${seconds}s to go`
})
// add event listeners to buttons
startBtn.addEventListener("click", () => timer.start());
stopBtn.addEventListener("click", () => timer.stop());
resetBtn.addEventListener("click", () => timer.reset());
})
<div id="countdown"></div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
I have two onclick functions that performing start and stop functions using socket when i click start it start requesting images from server(i am using intervals) when i click stop the server is stopped but the request keep coming to the server. the intervals seems not stopped after clicking kindly review my code
Code:
var interval;
function onclick() {
interval = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
function onclicks() {
clearInterval(interval);
setImage(blanked);
}
I have tried to add clearInterval function but it seems not working
Variables declared at the top level of a component are redeclared on each render (here every time you call setImage()) so interval is undefined by the time onclicks() is called.
In order to maintain the value through renders you need to either use state (useState) or a ref (useRef) which won't lose it's value. In this case, since you don't want renders attached to the value of interval a ref is more appropriate. useRef() returns an object with a current property in which your value will be stored.
const interval = React.useRef(null);
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
function onclicks() {
clearInterval(interval.current);
interval.current = null; // reset the ref
setImage(blanked);
}
You probably also want to avoid setting multiple intervals. You may already be doing so, but if not you can check if interval is null or not before setting a new one.
const interval = React.useRef(null);
function onclick() {
if (interval.current === null) { // only set new interval if interval ref is null
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350);
}
}
For me, the easiest way to solve this behavior was set the interval as a Ref:
import { useRef } from 'react';
// Initialize the interval ref
const interval = useRef();
// Create the interval
function onclick() {
interval.current = setInterval(() => {
socket.emit("get_image", "Click");
socket.on('send_image', (message) => {
setImage(message.image);
});
}, 350)
}
// Remove the interval
function onclicks() {
clearInterval(interval.current);
setImage(blanked);
}
Using this hook, the component will not re-render when you change the value remove/change the interval.
I'm creating a stopwatch that has a button to start and stop the time, but I'm having trouble with setInterval behavior.
When declared in React's functional component level, it will run once it's mounted.
Example:
const Timer = () => {
const timer = setInterval(() => console.log('running'), 1000)
}
When I declare it inside a function, it will not run until the function is called but then I can't get it to stop.
const Timer = () => {
const [start, setStart] = useState(false)
const startStopTimer = () => {
const timer = setInterval(() => console.log('running'), 1000)
}
return (<Button
onClick={() => {
setStarted(!start)
startStopTimer()
}
> Start/Stop </Button>)
}
I've then tried adding clearInterval() in the function and calling it conditionally if start === false. In this implementation, the first button click does nothing. Second click starts the timer, but it can't be stopped.
const Timer = () => {
const [start, setStart] = useState(false)
const startStopTimer = () => {
let timer = setInterval(() => console.log('running'), 1000)
if (!started) clearInterval(timer)
}
return (<Button
onClick={() => {
setStarted(!start)
startStopTimer()
}
> Start/Stop </Button>)
}
Ciao, I suggest you to modify your code like this:
const Timer = () => {
//const [start, setStart] = useState(false) do not use state for scripting reasons
let timer = null;
const startStopTimer = () => {
if(!timer) timer = setInterval(() => console.log('running'), 1000)
else {
clearInterval(timer)
timer = null
}
}
return (<Button
onClick={() => {
//setStarted(!start)
startStopTimer()
}
> Start/Stop </Button>)
}
Explanation: timer should be defined outside the startStopTimer otherwise, every time you launch startStopTimer, you create a new timer.
Ok this was easy, but now the important part: I strongly suggest you to not use react state for scripting reason. react state should be used only for rendering reasons. Why? Because hook are async and if you read start immediately after use setStart, you will read an old value.
timer variable is local and only scoped inside sartStopTimer() every time you call sartStopTimer() it generates new timer and when clearing timeout you are only clearing the recently generated one not previous timer
let timer;
const startStopTimer = () => {
timer = setInterval(() => console.log('running'), 1000)
if (!started) clearInterval(timer)
}
Made timer global variable.
This should get you started.
Try this:
const Timer = () => {
const [timerId, setTimerId] = useState(null)
function startStopTimer(){
if (timerId) {
clearInterval(timerId)
setTimerId(null)
} else {
setTimerId(setInterval(() => console.log('hello'), 1000))
}
}
return <button onClick={startStopTimer}> Start/Stop </button>
}
There is no need to use state, by the way. In case you do not need to keep the timer state you can definitely use plain variable, of course.
I am new to React so as part of learning I am trying to do a js debounce function when typed in input box (a search box simulation). But for some reason it is not working. The function is getting called each for key up than once for 2000 ms or delay specified in timeout. Below is the link to sandbox code for your reference. I have referred across blogs, the implementations seems the same yet I could not figure out what is the issue.
https://codesandbox.io/s/react-debouncing-9g7tc?file=/src/search.component.js
Issue :
const debounce = (func, delay) => {
let t; // <-- this will be new variable each time function get called
return function() {
clearTimeout(t); // <--- this also points to the new one, so it will clear nothing
// so all the previously called func will be called
t = setTimeout(() => func(), delay);
};
};
1st Solution : useRef
const t = useRef(null);
const debounce = (func, delay) => {
return function() {
clearTimeout(t.current); // <--- pointing to prev setTimeout or null
t.current = setTimeout(() => func(), delay);
};
};
WORKING DEMO
2nd Solution : Define t outside scope of SearchUi
var t;
const SearchUi = () => {
...
const debounce = (func, delay) => {
return function() {
clearTimeout(t);
t = setTimeout(() => func(), delay);
};
};
...
};
WORKING DEMO