Does Vue automatically remove all vue/vuex watchers on component destroy? - vue.js

If the component subscribes to the Vuex events like:
this.$store.watch or this.$store.subscribe
Is it necessary to remove the watcher during component destroy or Vue already takes care of it under the hood?
P.S: Current logic runs on 2.6.10 Vue version.

Following #Cristiano Soleti I checked if listeners are still being called after component that registered them was destroyed.
...and at least in 2.6.10 version, Vuex watcher are actually not removed automatically when component is destroyed. And thus should be unregistered explicitly by developer in beforeDestroy lifecycle hook.

The VueX docs are vague, however it does say "To stop watching, call the returned unwatch function." with no other information, which implies that watchers never get removed any other way: https://vuex.vuejs.org/api/#watch
So, you should manually remove a VueX watcher when destroying a component.
Here's an example:
MyComponent.vue
data: function() {
return {
myWatcher: null // Holds the watcher, so we can reference it
}
},
mounted() {
// Invoke the watcher
this.myWatcher = this.$store.watch(state => state.myStoreVariable, (valueNew, valueOld) => {
// Do stuff
console.log("valueOld: " + valueOld + ", valueNew: " + valueNew)
})
},
beforeDestroy () {
this.myWatcher() // Simply calling the watch holder as a function (ie with parentheses) will remove the watcher.
},

Related

Detect exiting route changes in Vue

I'm working on a component which enables the user to undo the deletion of an item. However, the item should be deleted when the user navigates to another route. To achieve this I'm watching the route like so:
`watch: {
$route(to, from) {
if (this.showUndo === true) {
console.log('item will be deleted');
this.confirmDelete();
}
},
},
`
Unfortunately, this gets only triggered when I enter this specific route and not on exiting it. An explanation why that is or an alternative to my watch: - method would be much appreciated!
Basically I'm looking for an alternative to beforeRouteLeave since this is a sub-component and therefore I can't use Navigation Guards. Thanks!
Vue lifecycle hook- BeforeDestroy is fired right before teardown. Your component will still be fully present and functional. If you need to cleanup events or reactive subscriptions,
beforeDestroy would probably be the time to do it.
<script>
export default {
beforeDestroy() {
//Try like this
this.confirmDelete();
console.log('item will be deleted');
}
}
</script>
Ref - https://v2.vuejs.org/v2/api/#beforeDestroy

Vue.js lifecycle hooks

I was sure that the lifecycle hooks in Vue were 8 (beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy, destroyed) but today I found out(https://v2.vuejs.org/v2/api/#activated) that there are 3 more:
• activated
• deactivated
• error captured
Somebody can explain how these 3 work? Is possible to test them with a console.log? (Just to understand when they are called)
First, a little context:
In Vue 2.0+, there is a built-in component called <keep-alive> that takes the child element inside it and keeps it alive in memory as a cached component. Normally, Vue would reuse a component if its props change, but maybe the component is very complex and is slow to update. You could wrap it with <keep-alive> and the component would be cached for the props provided to it.
When a component inside a <keep-alive> is updated, the activated life-cycle hook is called. When that component is cached and set aside, the deactivated life-cycle hook is called.
The errorCaptured life-cycle hook was added in Vue 2.5.0 and is called whenever an error is captured by a descendent component. So, if you have a component called A that has a child component called B, and that has a child component called C, then if C captures and error, the errorCaptured life-cycle hook will be called on both A and B.
These hooks all work the same as any other hook, so use them the same way.
export default {
data() {
return {}
},
mounted() {
console.log('mounted hook called')
},
errorCaptured(err, vm, info) {
console.log('error captured in component', vm)
console.error(err)
console.log('error info:', info)
},
activated() {
console.log('cached component is being used again')
},
deactivated() {
console.log('component is being kept alive in cache for now')
}
}
https://v2.vuejs.org/v2/api/#activated
https://v2.vuejs.org/v2/api/#deactivated
https://v2.vuejs.org/v2/api/#errorCaptured
I know it's already late for this answer but someone might also looking answer for
the problem. Vue.js disable's console.log function by default, so we have to enable it.
Just put "rules": { "no-console": "off",} on package.json
Cheers

Vue.js: how to use the afterEnter hook with an async component

I would like to use JS Hook as described here. Specially, I want to use the afterEnter hook with an async component.
This is my async component:
Vue.component('example', function(resolve, reject){
let data = {
text: 'test data',
};
$.post('http://example.com', data, function(r){
r = JSON.parse(r);
if( r.success ) {
resolve({
template: r.data,
afterEnter: function(el, done){
console.log('test');
}
});
}
});
});
This is what the ajax call gets from the server, and it's what is passed to the template in r.data.
<transition v-on:after-enter="afterEnter"></transition>
These are the two errors that I get.
[Vue warn]: Property or method "afterEnter" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
[Vue warn]: Invalid handler for event "after-enter": got undefined
Is it possible to use JS hooks with async components? And if not, how should I approach this? My objective is to run custom JS after Vue (and/or vue-router) inject the component template onto the page, so that I can initiliaze image sliders and whatnot. It is important that my custom JS fires every time the component is navigated to, and not only on the first load.
Thank you.
That warning means that Vue is looking for (but unable to find) a property or method named "afterEnter", which you reference in your template. You have defined afterEnter in your resolve function as if it is a lifecycle hook, but it needs to be one of your Vue instance's methods.
So, your resolve function should look like this:
resolve({
template: r.data,
methods: {
afterEnter: function(el, done) {
console.log('test');
}
}
});

Confused with Vuex commit/dispatch in simple VueJS test

From the book:
To invoke a mutation handler, you need to call store.commit with its type: store.commit('increment')
Mutations must always be synchronous.
From the book:
Actions commit mutations ( can be asynchronous )
Actions are triggered with the store.dispatch method: store.dispatch('increment')
So it's action -> mutation -> new state most of the time.
So what's confusing me, is the very simple example, whereby I'm trying to show the asynchronous result of an object getTest
See this pen
Why can't Vue see that I'm not calling a mutation, but an action when the component loads?
What is this "book" you are talking about? I'm asking because you are using a mix of new Vuex 2.* and old, Vuex 1.* syntax, which is not working anymore in 2.*, so I assume you are learning in part from outdated resources.
(Sidenote: Why are you using Vue 1? Vue2.* has been out for over 10 months now...)
Your action and mutation definitions are correct, but there's no vuex: {} key in components anymore in Vuex 2.*
Also, you are trying to dispatch an action 'INCREMENT', but oyour store only has a mutation by that name, no action. So you have to use commit rather than dispatch.
Instead, you directly add computed props and method to your instance, or use the map*helpers provided by Vuex:
var vm = new Vue({
el: '[vue=app]',
data: {
welcome: 'Testing Vuex'
},
store: myStore,
created() {
this.$store.dispatch(FETCH_TEST_STATE)
},
computed: {
...Vuex.mapState( {
count: state => state.count,
getTest: state => state.testState
}),
}
methods: {
increment({dispatch}) {
this.$store.commit('INCREMENT', 1)
}
}
})
the action you call from created doesn't work because async/await was acting up on codepen
the commit called from this action didn't set any state.
Fixing all of this, here's your working example:
https://codepen.io/LinusBorg/pen/NvRYYy?editors=1010

vue 2 lifecycle - how to stop beforeDestroy?

Can I add something to beforeDestroy to prevent destroying the component? ?
Or is there any way to prevent destroying the component ?
my case is that when I change spa page by vue-route, I use watch route first, but I found that doesn't trigger because the component just destroy..
As belmin bedak commented you can use keep-alive
when you use keep-alive two more lifecycle hooks come into action, they are activated and deactivated hooks instead of destroyed
The purpose of keep-alive is to cache and to not destroy the component
you can use include and exclude atteibutes of the keep-alive element and mention the names of the components that shoulb be included to be cached and be excluded from caching. Here is documentation
in case you want to forecefully destroy the component even if its cached you can use vm.$destroy() here
Further you can console.log in all the lifecycle hooks and check which lifecycle hook is being called
You can use vue-route navigation-guards, so if you call next(false) inside the hook, navigation will be aborted.
router.afterEach((to, from) => {
if(your condition){
next(false) //this will abort route navigation
}
})
According to this source: https://router.vuejs.org/guide/advanced/navigation-guards.html
I suggest you to do something like this with your Vue router:
const router = new VueRouter({ }); // declare your router with params
router.beforeEach((to, from, next) => {
if(yourCondition){
next(false); // prevent user from navigating somewhere
} else {
return next(); // navigate to next "page" as usual
}
});
This will prevent destroying your Vue instance on your declared condition, and it will also prevent user from navigating to another page.
Although I would consider #Vamsi Krishna "keep-alive" answer to be the proper "VueJS way" to solve this issue, I was not willing to refactor part of my code for it.
I also couldn't use the Vue router navigation guard "as-is" because in the case of beforeRouterLeave, even though using next(false) prevented the route from continuing, the component in Vue was ALREADY destroyed. Any state I had that wasn't saved would be lost, which defeats the purpose of cancelling the route change.
This wasn't what I wanted, as I needed the state of the form/settings in the component to remain (the component reloaded itself and kept the same route).
So I came up with a strategy that still used a navigation guard, but also cached any form changes/settings I had in the component in-memory, eg. I add a beforeRouteLeave hook in the component:
beforeRouteLeave (to, from, next) {
if (!this.isFormDirty() || confirm('Discard changes made?')) {
_cachedComponentData = null // delete the cached data
next()
} else {
_cachedComponentData = this.componentData // store the cached data based on component data you are setting during use of the component
next(false)
}
}
Outside the Vue component, I initialize _cachedComponentData
<script>
let _cachedComponentData = null
module.exports = {
...component code here
}
<script>
Then in the created or mounted life cycle hooks, I can set the _cachedComponentData to "continue where the user left off" in the component:
...
if (_cachedComponentData) {
this.componentData = _cachedComponentData
}
...