I've been trying to set-up a simple countdown using Vue3, but I cant make it to work properly. This would be so easy to make in React, but I simply dont understand the logic in Vue (3). So, I've come up with this:
<script>
export default {
data() {
return {
timer: 10,
interval: ""
}
},
emits: ["start-game"],
methods: {
startGame() {
this.$emit("start-game")
this.startTimer()
},
startTimer() {
clearInterval(this.interval)
while(this.timer != 0) {
this.interval = setInterval(() => {
this.timer--
}, 1000)
}
}
}
}
</script>
You would expect this to work but it creates an infinite loop. Somehow, you cant just use while inside of vue methods. If I remove the while it actually counts down indefinitely, but I need other functions to run once the timer runs out (hits 0). What is the Vue way to handle such things?
Don't think this has anything to do with React or Vue. You need to clear your interval in the setInterval callback so that it knows when to stop. No need for the while loop:
this.interval = setInterval(() => {
if (this.timer === 0) {
clearInterval(this.interval)
} else {
this.timer--
}
}, 1000)
Also checkout this pure js example:
let timer = 10;
let interval = setInterval(() => {
if (timer === 0) {
clearInterval(interval)
} else {
timer--
console.log(timer)
}
}, 1000)
I got the same trouble, I was super happy in react to have react-hook-timer but nothing in vue 3 .
So i made it you have 3 main features:
timer
stopwatch
time
For your use case, timer is the one you need, follow these steps:
npm i vue-timer-hook
<script setup>
import { useTimer } from "vue-timer-hook";
const timer = useTimer(10 * 1000, false);
const startGame = () {
timer.start()
}
watchEffect(() => {
if(timer.isExpired.value) {
console.warn('IsExpired')
}
})
</script>
https://github.com/riderx/vue-timer-hook
To get this to work I did this in mount():
let this_ = this; // to make this available as a closure
setInterval(() => {
this_.$options.timer.bind(this_)(); // methods are available in $options, need to bind `this`
}, 1000)
Related
I am trying to mock a setInterval inside my created hook but no matter what I try
the function is never called. What I have done so far is using jest.useFakeTimers and inside
each test I would use jest.advanceTimersByTime(8000) to check if my api is being called.
I would appreciate any opinions/help. thanks
my vue file
created() {
setInterval(() => this.checkStatus(), 8000)
},
methods: {
async checkStatus() {
let activated = false
if (!this.isLoading) {
this.isLoading = true
let res = await this.$UserApi.getUserActivateStatus(this.accountId)
this.isLoading = false
if (res.success) {
activated = res.activated
}
if (activated) {
console.log("activated")
} else {
console.log("error")
}
}
}
}
my test file
import { shallowMount, config } from "#vue/test-utils"
import Step4 from "../../../login/smart_station/step4"
describe("Step4", () => {
let wrapper
const $route = {
query: {
account_id: "99"
}
}
const mockGetUserActivateStatus = jest.fn(() =>
Promise.resolve({ success: true, activated: true })
)
beforeEach(() => {
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
jest.useFakeTimers()
})
it("activates status every 8secs", async () => {
jest.advanceTimersByTime(9000)
expect(mockGetUserActivateStatus).toHaveBeenCalled()
})
})
Jest's Timer Mocks replace the native timer functions like setInterval with their own versions that can be controlled.
Your problem is that you are telling Jest to replace these functions after your component is created and mounted. Since you're using setInterval within your component's created hook, this will still be using the real version.
Move the jest.useFakeTimers() to the top of the beforeEach setup function
beforeEach(() => {
jest.useFakeTimers()
wrapper = shallowMount(Step4, {
mocks: {
$UserApi: {
getUserActivateStatus: mockGetUserActivateStatus
}
}
})
})
How would use Jest Test to test this method:
delayedFetch() {
setTimeout(() => {
this.fetchData();
}, 1000);
I have tried using Async and await but I'm prob using it wrong.
It's hard to test code with side effects, and you did not provide the entire context, but I try to help.
I think the this in the this.fetchData() inside the setTimeout is referencing the delayedFetch method itself. (I don't know it is your intention, as far as How I use vue.js
But anyway you can find, how to test setTimeouts here link to jest doc
Here is a simple implementation
const someObj = {
// I assume the delayedFetch is some method of an object
fetchData() {
return "some-data";
},
delayedFetch() {
const vue = this;
setTimeout(() => {
vue.fetchData();
}, 1000);
}
}
jest.useFakeTimers();
test("delayedFetchTest", () => {
someObj.delayedFetch();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
})
I use web worker with vuejs to do some complicated calculations. I have two ways to terminate workers.
In beforeDestroy lifestyle
export default {
beforeDestroy() {
this.worker && this.worker.terminate()
},
watch: {
data(data) {
if (!this.worker) {
this.worker = new Worker('worker_url')
this.worker.addEventListener('message', () => {
// do something
})
}
this.worker.postMessage(data)
}
}
}
After receiving message
export default {
watch: {
data(data) {
const worker = new Worker('url')
worker.addEventListener('message', () => {
// do something
worker.terminate()
})
worker.postMessage(data)
}
}
}
And, in this example, worker creates a thread and keep until terminate method being called.
I wanna know which method is better and why.
Thanks.
I have a VueJS slideshow component which uses setInterval.
I am having major issues simply calling clearInterval on it!
data() {
return {
slideIndex: 1,
}
},
mounted() {
this.showSlides(this.slideIndex);
this.startTimer();
},
methods: {
// Timer
startTimer() {
timer = setInterval(() => {
this.plusSlides(1);
}, 4500);
},
invalidateTimer() {
clearInterval(timer);
},
// Next/Prev controls
plusSlides(n) {
this.invalidateTimer();
this.showSlides((this.slideIndex += n));
},
Calling this.invalidateTimer() does nothing.
I've also tried with timer being on the component's data, same result, clearInterval has no effect.
I'm aware this business logic is bogus, but would just like to see clearInterval working as desired.
How can I stop the loop?
Found a work around ... you can keep the timer on the component's data, determine it's id, then in invalidateTimer you can loop through every Timeout on window and find it ...
data() {
return {
slideIndex: 1,
timer: '',
timerId: ''
}
},
mounted() {
this.showSlides(this.slideIndex);
this.startTimer();
},
methods: {
// Timer
startTimer() {
this.timer = setInterval(() => {
this.plusSlides(1);
}, 4500);
this.timerId = this.timer._id
},
invalidateTimer() {
var ids = window.setTimeout(function() {}, 0);
while (ids--) {
if (ids == this.timerId) {
window.clearTimeout(ids);
break;
}
}
},
// Next/Prev controls
plusSlides(n) {
this.invalidateTimer();
this.startTimer();
this.showSlides((this.slideIndex += n));
},
My business logic has been updated. It invalidates the timer upon prev/next being clicked, then simply resets the timer so it will auto slide 4500ms later.
How can i destroy this watcher? I need it only one time in my child component, when my async data has loaded from the parent component.
export default {
...
watch: {
data: function(){
this.sortBy();
},
},
...
}
gregor ;)
If you construct a watcher dynamically by calling vm.$watch function, it returns a function that may be called at a later point in time to disable (remove) that particular watcher.
Don't put the watcher statically in the component, as in your code, but do something like:
created() {
var unwatch = this.$watch(....)
// now the watcher is watching and you can disable it
// by calling unwatch() somewhere else;
// you can store the unwatch function to a variable in the data
// or whatever suits you best
}
More thorough explanation may be found from here: https://codingexplained.com/coding/front-end/vue-js/adding-removing-watchers-dynamically
Here is an example:
<script>
export default {
data() {
return {
employee: {
teams: []
},
employeeTeamsWatcher: null,
};
},
created() {
this.employeeTeamsWatcher = this.$watch('employee.teams', (newVal, oldVal) => {
this.setActiveTeamTabName();
});
},
methods: {
setActiveTeamTabName() {
if (this.employee.teams.length) {
// once you got your desired condition satisfied then unwatch by calling:
this.employeeTeamsWatcher();
}
},
},
};
</script>
If you are using vue2 using the composition-api plugin or vue3, you can use WatchStopHandle which is returned by watch e.g.:
const x = ref(0);
setInterval(() => {
x.value++;
}, 1000);
const unwatch = watch(
() => x.value,
() => {
console.log(x.value);
x.value++;
// stop watch:
if (x.value > 3) unwatch();
}
);
For this kind of stuff, you can investigate the type declaration of the API, which is very helpful, just hover the mouse on it, and it will show you a hint about what you can do: