Reputation: 21
I'm attempting to make a basic count-down timer in React. It should start at 30 seconds, and count down by 1 each second. When clicking the button, the timer should restart at 30 and begin the countdown again.
This seems simple enough, and it's printing to the console exactly as I expect it to. However, as soon as I try to update state with my timer so I can render the countdown on-screen (uncomment the commented line below) the console.log duplicates, the render doubles, and I seem to have two states running simultaneously. I'm not sure what to do about this.
Any help is greatly appreciated!
const [seconds, setSeconds] = useState(30)
let interval = null
function startTimer() {
stopTimer()
let start = 30
interval = setInterval(() => {
// setSeconds(start)
console.log('start: ', start)
start--
}, 1000)
}
function stopTimer() {
clearInterval(interval)
}
return (
<p>{seconds}s</p>
<button onClick={startTimer}>Start</button>
)
I've looked around to see what I could find myself before posting. I read through a number of articles on React and setInterval, and watched some tutorials, but couldn't find what I was looking for. I attempted to rewrite the code in different ways but always ended with the same result.
Upvotes: 1
Views: 840
Reputation: 21
Thanks for the help folks!
Turns out, regardless of how I assign the state to seconds (arrow function or a separate variable) the cause of the issue here was the placement of the 'interval' variable.
Thomas's answer was correct. Moving it outside of the functional component rather than inside corrected the issue. If the variable was within the function the interval seemed like it didn't fully clear, just paused, and then there were two intervals running simultaneously.
Here's the final code, functioning as expected.
import { useState } from "react"
let interval = null
export default function app() {
const [seconds, setSeconds] = useState(30)
function startTimer() {
stopTimer()
interval = setInterval(() => {
setSeconds((seconds) => seconds - 1)
}, 1000)
}
function stopTimer() {
clearInterval(interval)
setSeconds(30)
}
return (
<p>{seconds}s</p>
<button onClick={startTimer}>Start</button>
</div>
)
}
Upvotes: 0
Reputation: 1967
There are multiple things to say, like why use async/await when there is nothing to await for, why use a local variable start = 30
when you just want to decrease your seconds count and why you declare the interval in the function body. A React functional component will run all its code and in your case do let interval = null
everytime it rerenders. You have to store the interval somewhere else, like here as a global variable. Moreover, when you create the setInterval
, it won't have access to the new seconds
count. What you can do is use the arrow function form inside your setState
function. Doing so, you will get the right seconds
variable.
Maybe the code below will help you find out what's wrong:
let interval = null
function App(props) {
const [seconds, setSeconds] = React.useState(30)
function startTimer() {
stopTimer()
interval = setInterval(() => {
setSeconds((seconds) => seconds - 1)
}, 1000)
}
function stopTimer() {
clearInterval(interval)
setSeconds(30)
}
return (<button onClick={startTimer}>{seconds}</button>)
}
Upvotes: 1