VueJS setInterval clearInterval issue - vue.js

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.

Related

How to full state before going throw script in component vue

Mey be it is simple, but I'm new in frontend. I have a page component. And I need to fetch data before component calculated.
import {mapActions, mapGetters} from 'vuex'
export default {
name: "notFoundPage",
methods: {
...mapActions([
'GET_SUBCATEGORIES_FROM_CATEGORIES'
]),
},
computed: {
...mapGetters([
'SUBCATEGORIES'
]),
subCategories() {
// doing some calculations with already updated SUBCATEGORIES in store
}
return result;
}
},
created() {
this.GET_SUBCATEGORIES_FROM_CATEGORIES()
> **// from here we go to store**
},
mounted() {
this.GET_SUBCATEGORIES_FROM_CATEGORIES()
}
}
store:
let store = new Vuex.Store({
state: {
categories: [],
subcategories: []
},
mutations: {
SET_CATEGORIES_TO_STATE: (state, categories) => {
state.categories = categories;
},
SET_SUBCATEGORIES_TO_STATE: (state, subcategories) => {
state.subcategories = subcategories;
}
},
actions: {
GET_CATEGORIES_FROM_API({commit}) {
return axios('http://localhost:3000/categories',
{
method: "GET"
})
But here compiler returns to component. I do not have any idea, why it is not finishing this action. And after calculating the computed block in component it returns to this point. But I need 'SET_CATEGORIES_TO_STATE' already updated
.then((categories) => {
commit('SET_CATEGORIES_TO_STATE', categories.data)
return categories;
}).catch((error) => {
console.log(error);
return error;
})
},
GET_SUBCATEGORIES_FROM_CATEGORIES({commit}) {
this.dispatch('GET_CATEGORIES_FROM_API').then(categories => {
let subs = categories.data.map(function(category) {
return category.subcategories.map(function(subcategory) {
return subcategory.name
})
})
commit('SET_SUBCATEGORIES_TO_STATE', subs)
return subs
})
}
},
getters: {
CATEGORIES(state) {
return state.categories;
},
SUBCATEGORIES(state) {
return state.subcategories;
}
}
if you have difficulties with timings and async tasks, why don't you use async/await?
you want to wait in a async function (for example calling a backend for data) till the data is fetched. then you want to manipulate/delete/change/add, do what ever you want with that data and display the result on screen.
the point is, Vue is a reactive Framework, which means it rerenders (if the setup is correct made) the content by itself after what ever calculation is finished. so don't worry about something like that.
to be honest, the question is asked really weird. and your code is hard to read. sometimes moving two steps back and try a other way isn't false as well.

How to make a simple countdown in Vue3?

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)

Setting intial local data from Vuex store giving "do not mutate" error

I thought I understood the correct way to load inital state data from Vuex into the local data of a component, but why is this giving me “[vuex] do not mutate vuex store state outside mutation handlers.” errors! I am using a mutation handler!
I want my component data to start empty, unless coming back from a certain page (then it should pull some values from Vuex).
The component is using v-model=“selected” on a bunch of checkboxes. Then I have the following:
// Template
<grid-leaders
v-if="selected.regions.length"
v-model="selected"
/>
// Script
export default {
data() {
return {
selectedProxy: {
regions: [],
parties: [],
},
}
},
computed: {
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
this.selectedProxy = newVal
// If I remove this next line, it works fine.
this.$store.commit("SET_LEADER_REGIONS", newVal)
},
},
},
mounted() {
// Restore saved selections if coming back from a specific page
if (this.$store.state.referrer.name == "leaders-detail") {
this.selectedProxy = {...this.$store.state.leaderRegions }
}
}
}
// Store mutation
SET_LEADER_REGIONS(state, object) {
state.leaderRegions = object
}
OK I figured it out! The checkbox component (which I didn't write) was doing this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
if (index == -1) {
this.value.regions.push(region)
} else {
this.value.regions.splice(index, 1)
}
this.$emit("input", this.value)
},
The line this.value.regions.push(region) is the problem. You can't edit the this.value prop directly. I made it this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
let regions = [...this.value.regions]
if (index == -1) {
regions.push(region)
} else {
regions.splice(index, 1)
}
this.$emit("input", {
...this.value,
regions,
})
},
And then I needed this for my computed selected:
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
// Important to spread here to avoid Vuex mutation errors
this.selectedProxy = { ...newVal }
this.$store.commit("SET_LEADER_REGIONS", { ...newVal })
},
},
And it works great now!
I think the issue is that you can't edit a v-model value directly, and also you also have to be aware of passing references to objects, and so the object spread operator is a real help.

Does the "before" hook trigger a render when leveraging transition hooks in a render function?

I am trying to create a movement transition within a render function using appear hooks. I thought that putting initial state of the transition within the beforeAppear hook and then setting the finalized state in the appear hook would cause an element to be enough for a transition to occur.
However, what seems to be happening is that the style that I set in the beforeAppear either never renders, or is overwritten in the appear hook before the browser knows that it should transition.
Below is an example of what I am trying to accomplish. Using Vue.nextTick() does not give me what I was hoping. However, I do see that using a setTimeout() will give me what I am effectively looking for, but I thought that this was the built-in functionality of the appear hook considering the docs Transition Classes:
v-enter: Starting state for enter. Added before element is inserted, removed one frame after element is inserted.
v-enter-active: Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when
transition/animation finishes. This class can be used to define the
duration, delay and easing curve for the entering transition.
I understand that these are for the classes that are applied to the element, but don't these map 1:1 with the hooks?
const app = new Vue({
el: "#app",
render(h) {
// Just hooks
const transitionTestJustHooks = h("transition", {
props: {
css: false,
appear: ""
},
on: {
beforeAppear(el) {
console.log("before-appear");
el.style.position = "absolute";
el.style.transition = "left 2s ease";
el.style.top = "0em";
el.style.left = "20px";
},
appear(el, done) {
console.log("appear");
el.style.left = "200px";
done();
}
}
}, [h("div", "just-hooks")]);
// Vue.nextTick()
const transitionTestNextTick = h("transition", {
props: {
css: false,
appear: ""
},
on: {
beforeAppear(el) {
console.log("before-appear");
el.style.position = "absolute";
el.style.transition = "left 2s ease";
el.style.top = "1em";
el.style.left = "20px";
},
appear(el, done) {
console.log("appear");
Vue.nextTick(() => {
el.style.left = "200px";
done();
});
}
}
}, [h("div", "Vue.nextTick()")]);
// setTimeout()
const transitionTestSetTimeout = h("transition", {
props: {
css: false,
appear: ""
},
on: {
beforeAppear(el) {
console.log("before-appear");
el.style.position = "absolute";
el.style.transition = "left 2s ease";
el.style.top = "2em";
el.style.left = "20px";
},
appear(el, done) {
console.log("appear");
setTimeout(() => {
el.style.left = "200px";
done();
});
}
}
}, [h("div", "setTimeout")]);
return h("div", [
transitionTestJustHooks,
transitionTestNextTick,
transitionTestSetTimeout
]);
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app"></div>

Execute code after view update in vue.js

I want to perform a task (scrolling to bottom, since new elements where added) after a view has been updated by vue.
Here is my code:
export default {
mounted() {
this.connect();
},
connection: null,
methods: {
connect: function () {
...
},
onMessage: function (msg) {
this.messages.push(msg);
this.scrollDown();
return true;
},
scrollDown: function () {
$("html, body").animate({
'scrollTop': $(this).offset().top
}, 100);
}
As you can see, the this.scrollDown(); is invoked after this.messages.push(msg);, so since the view is not updated immediately the scroll is not well performed.
How is it supposed to be made?
Try waiting for the DOM to be updated using:
this.$nextTick(function () {
this.scrollDown();
});