I am attempting to set up a button event in Vue 3 that displays a setTimeout countdown on the button before routing to another page. My event function includes a conditional that executes a countdown from 5 to 0 when the countValue is > 0. The setTimeout function is recursive in that it calls the goHome() function each time to execute a countdown. When the countValue is 0, then router.push is called to re-route to another page. My issue is that when I press the button, the countdown immediately lumps from 5 to 4. I am wanting to briefly delay the countdown when I first push the button. In other words, I do not want the countdown to immediately jump to 4 when I push the button. How can I fix this?
Here is my code so far:
Template:
<div>
<button @click="goHome" type="button" :disabled="disabledOnCount === true ? true : false">Go Home in {{ countValue }} seconds</button>
</div>
Script:
import { ref } from '@vue/reactivity'
const countValue = ref(5)
const goHome = () => {
if (countValue.value > 0) {
disabledOnCount.value = true
countValue.value -= 1
setTimeout(() => {
goHome()
if (countValue.value === 0) {
router.push({ name: 'home' })
}
}, 1000)
}
}
CodePudding user response:
The countdown jumps to 4 immediately because goHome()
immediately decrements the value, and then calls setTimeout()
to check the value. Changing the countdown value and reading it should happen in the same tick:
const goHome = () => {
if (countValue.value > 0) {
disabledOnCount.value = true
countValue.value -= 1 // ❌ decremented immediately
setTimeout(() => {
goHome()
if (countValue.value === 0) {
router.push({ name: 'home' })
}
}, 1000)
}
}
Solution
A few changes:
goHome()
should always callsetTimeout()
so that its effect is always delayed.The timer callback should only call
goHome()
when the countdown is nonzero. Otherwise, the callback has no effect, so no need to call it.
const goHome = () => {
disabledOnCount.value = true
1️⃣
setTimeout(() => {
if (countValue.value > 0) {
countValue.value -= 1
if (countValue.value === 0) {
router.push({ name: 'home' })
} else {
2️⃣
goHome()
}
}
}, 1000)
}
If it's possible for the page to be unmounted before the timer finishes (e.g., when the user changes the page), the timer should be stopped on unmount to free resources. Otherwise, the timer can continue running while the user has navigated away, and then suddenly, they're pushed to the home page unexpectedly.
To stop the timer, you have to track the timer ID, and then use the onUnmounted
hook to clearTimeout()
on that ID:
import { onUnmounted } from 'vue'
let countdownTimerId = null
const goHome = () => {
countdownTimerId = setTimeout(() => {
/* same as above */
if (countValue.value === 0) {
countdownTimerId = null
}
}, 1000)
}
onUnmounted(() => clearTimeout(countdownTimerId))