Having mounted() only run once on a component Vue.js - javascript

I have two components that conditionally render with v-if:
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-if="this.mode !== 'mode'"/>
I have load-in animations for both components that I have under mounted(), that I only want to run the first time they are loaded. But with mounted, each time the component is recreated when this.mode changes, the animations trigger again. How can I avoid this?

You could wrap your components within a keep-alive element ..
<keep-alive>
<Element v-if="this.mode === 'mode'"/>
<OtherElement v-else />
</keep-alive>

If created() doesn't do the job, you should try to do a simple check in the parent element if this.mode was switched on and off before, save the result as a variable and pass that to the mounted hook and only run the animation if the mode wasn't switched before.

Using v-if, re-renders components every time the this.mode changes. Vue stores them in virtual DOM, but re-renders them if you use v-if.
If you have access to code for these components, consider using prop for v-show and watching it, in optional combination with emitso you can communicate between parent and child components, and set flag if you need it in child and in parent component if child component loads animation initially, to avoid loading it all over again.
This would be one of the child components:
<template>
<div v-show="mode === 'themode'">
</div>
</template>
<script>
export default {
props: {
mode: {
type: Boolean,
required: true,
twoWay: true
},
},
data() {
return {
animationLoaded : false,
}
},
mounted(){
},
watch: {
'mode' : function(){
if(this.mode === 'mode' && this.animationLoaded === false){
//load animation and set flag to true, to avoid loading it again.
this.animationLoaded = true;
this.$root.$emit('component-name:animation-loaded');
}
}
},
...
And putting child in parent component:
<child-component :mode.sync="mode"></child-component>

I had a similar problem, I only wanted something in my mounted to run on page load, but when I navigated away (using vue router) and back again, the mounted would run each time, re-running that code. Solution: I put a window.addEventListener('load', () => {}) in the created function, which runs that code instead.
The created function runs early on in the vue bootstrapping process, whereas the window.load event is the last event in the page load queue. As vue runs as a single page app, the window.load event will only fire once, and only after all the javascript (including vue) has got itself up and running (in a sense). I can route around my environment back and forth knowing that the initial build scripts would only be run when the page is actually re-loaded.
I have a feeling there is a more vue-tiful way of doing this, or I am otherwise missing something that vue-terans would chastise me for, but at this point vue has slapped me on the wrist far too many times for me to bother trying anymore. Hacky workarounds it is!

Related

How to detect when vue component is after update function by property?

I need to display a spinner in vue for every component (this is the requirement).
For that I think about to do v-if="loading" inside component HTML.
My question is how to detect when component is loading complete? (meaning after the DOM is rendered, and the data-bind is resolved to the DOM elements)
According to Vue lifecycle when update function is trigger then the render is complete.
So for that I'll need to implement for every component update function that change the loading to false. is there eazy way to do that? for example one place to write the update function? can I do that without implement extends? any sophisticated way to do that?
What I need for example is this.loading available in Vue instance.
Of course you can do it. Vue mixins come in rescue.
Mixins are a flexible way to distribute reusable functionalities for
Vue components. A mixin object can contain any component options. When
a component uses a mixin, all options in the mixin will be “mixed”
into the component’s own options.
Notice that you should use mounted hook if you want to track when the component is inserted into DOM.
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
mixin.js:
export default {
data() {
return {
loading: true
}
},
mounted() {
console.log('I have been mounted')
this.loading = false
}
}
And then register that mixin globally, so it will be available in all components:
import mixin from './mixin'
Vue.mixin(mixin)

VueJS hook mounted() is not called when page is re-open by vue-router

Please, look the flux below, it's shows my problem. I'm using vue-router with this.$router.push to browsing on pages. I'm starting on PageA.
PageA -> PageB ( mounted() of PageB is called)
PageB -> PageA (returning to PageA)
PageA -> PageB (mounted() of PageB is not called)
It sounds that page (.vue component) is not closed and mainted on cache or other thing. I must use the mounted() method every time that page is open and maybe close the page and clean from cache. How I can solve it?
vue will re-use components were possible, this is expected.
Normally you would watch for route changes and update your component state accordingly.
To react to route changes you can use beforeRouteUpdate():
const Example = Vue.extend({
template: `
<div>
<p>This changes: '{{param}}'</p>
</div>`,
data(){
return {
param: this.$route.params.param
};
},
beforeRouteUpdate(to, from, next) {
this.param = to.params.param;
next();
}
});
const router = new VueRouter({
routes: [
{
path: '/:param', component: Example,
}
]
})
const app = new Vue({ router }).$mount('#app')
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/foo">foo</router-link><br>
<router-link to="/bar">bar</router-link><br>
<router-view></router-view>
</div>
Alternatively you can also watch the route and update the state accordingly:
Vue.extend({
watch: {
'$route'() {
// TODO: react to navigation event.
// params cotains the current route parameters
console.log(this.$route.params);
}
},
// ....
});
The vue-router documentation has a few great examples for this: Data Fetching - Vue Router
If you still want to use mounted(), you can do so by giving your router-view a key that will change when the route changes, e.g.:
<router-view :key="$route.fullPath"></router-view>
This will force the component to be re-created every time, so it does have a performance penalty - i would recommend using the route hooks described above, if possible.
Vue 3
For those that ended up here like me looking at why I'm not getting unmounted called on a component inside a page, when route changes, here's some guidance:
Let's say you have Page A that uses Component A in it, and Page B that does not use Component A.
When you open Page A for the first time, Component A will have, as expected, mounted method called.
However, when you navigate to Page B, Vue3 won't un-mount or destroy Component A, rather it will deactivate it, and deactivated method will be called instead of unmounted.
Once you navigate back to Page A, Component A will be reactivated and method activated will be called instead of mounted.
For those who are using the Composition API for Vue 3:
This use case is for fetching data from an API upon component mount and other dependencies. Instead of watch, you need to use watchEffect to automatically track the dependencies and perform side effects on mount.
watchEffect(fetchData);
watch(dependencies, callback) will trigger on first visit to the route. But if you go to another page, then come back to it, it won't trigger again because it does not count the initial state as an update. Also, the dependencies technically did not change from the time it was mounted.
Additional Notes:
If you are coming from React, watch is not exactly equivalent to useEffect. watchEffect is more similar to it.
For comparison, this code should have the same results as the watchEffect one. But watchEffect is more concise.
onMounted(fetchData);
watch([dependency 1, dependency 2, ...], fetchData);

Vue watch unexpectedly called twice

udpate #2: It is not a bug, it was my own fault. I extended the BaseComponent by a extend() method as well as putting extends: BaseComponent into the options. I thought I needed both, but extends: BaseComponent in the options seems to be enough.
So the double "extend" has apparently duplicated the watcher which lead to the strange behavior I documented in my question.
update: I found out what causes this problem: The watcher seems to be duplicated, because it's in a BaseComponent which is extended by the Component which is used in my example.
so export default BaseComponent.extend({ name: 'Component', ...}) seems to duplicate the watch object instead of "merging" it - there is now one in the BaseComponent (where it is implemented initially) and one in the Component - and of course both react to prop-updates.
This seems to be a bug IMHO.
Using vue-cli with single file components.
I am setting a prop in one component via a method:
<template>
<div>
<other-component :my-object="myObject" />
</div>
</template>
<script>
export default (Vue as VueConstructor).extend({
data() {
return {
myObject: null
}
},
methods: {
actionButtonClicked(action, ID) {
console.log('actionButtonClicked');
this.myObject = {
action: action,
ID: ID
}
}
}
});
</script>
then I am watching the prop in the other component with a watcher - but watch gets called twice on every execution of the method.
<script>
export default (Vue as VueConstructor<Vue>).extend({
/* ... */
props: {
myObject: {
type: Object,
default: null
},
watch: {
'myObject.ID'(value, oldValue) {
console.log('watcher executed');
}
}
/* ... */
});
</script>
so in the console i get the output:
actionButtonClicked
watcher executed
watcher executed
.. every time the method gets called.
I already tried all different variants of watchers - for example with deep: true + handler. but this all didn't change anything about the watcher being called twice.
In my case, I have the "watch" in a component and use the component twice on the page, so the "watch" is being registered twice.
Hopefully, it'll help somebody that has the same problem.
My watcher was duplicated because I was extending my BaseComponent in two ways:
by the extend() method of the component itself
by putting extends: BaseComponent into the options of the "outer" component
I thought you needed to use both pieces of code to extend another component, but apparently this is wrong and can lead to bad side effects.
You might want to check your component tree using a tool like the vue console or manually follow the list of imported components on your page in order to ensure no component is inadvertently imported twice.
I had a modal in the default layout, but wanted to display the modal body/main contents on a certain page (i.e without being triggered in a modal). I ended up importing both, and vue correctly treated them as separate instances. Which meant weird behavior like the modal taking two clicks to disappear, and of course, their watchers getting called twice

vuejs mounted is called even if component was not loaded via v-if

I am using vuejs for my app look like this
app.vue
<template>
<div>
<custom-elem v-if="somefalseCondition"> </custom-elem>
</div>
</template>
CustomElem.vue
<template>
some code
</template>
<script>
export default {
mounted(){
console.log('demo')
}
}
</script>
I am expecting this mounted should be called when element is actually rendered on screen , but it is executing even after condition inside v-if is false , what am i missing here ?
That is not correct, there must be a mistake somewhere.
v-if="false" avoid the creation of the component instance.
No event (beforeCreate, created, mounted...) is triggered if v-if is allways falsy.
Are you sure that your condition is allways falsy?
This must be the case since the beginning of your parent component, be careful about changing values, if any data passing inside your condition is not falsey at some point of time, component will be instanciated and events raised.
Regards
I think instead of using mounted() the lifecycle hook you are looking for is created(). if you use console.log() in created(), it'll not be called.
I can confirm this, even if you outsource it to a portal. Everything you do in the mounted hook of the component, is actually "doing his job." You simply don't see it until the template is rendered. One way to fix this is to place the function you run in the mounted hook inside the if() condition itself.
For example, I had the function that called e.preventDefault on the mousemove event, even though the component was in v-if and wasn't rendered, it still messed up my input range slider, I couldn't drag it as expected.
Well according to documentation here , It reads
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
Note that where it says where instance is mounted and not when template is rendered, which basically means as your component is called the component class will be loaded and mounted hook will be called. It makes sense because when your v-if becomes true, merely its template will be added and actual component class will not be loaded

Vue JS re-create the root component

I need to destroy and re-create the root application component. This is my template:
<div id="app">
{{ num }}
</div>
And this is my code:
if (app) {
app.$destroy();
} else {
app = new Vue(Object.assign({
el: '#app',
}, { data: { num: Math.random() } }))
}
This code runs on button click, but it doesn't clean the template and refresh the new data I'm passing. Any idea why?
I'm pretty sure that your attempt so solve a problem in a way that it should not be solve, or that you use vue in the wrong way.
But to you problem, when you execute this code:
if (app) {
app.$destroy();
}
Then the documentation state:
Completely destroy a vm. Clean up its connections with other existing vms, unbind all its directives, turn off all event listeners.
So they don't say that the element to wich the vue app is attached to has to or will change. If you want to clean up the DOM then you need to delete the content manually.
After app.$destroy() the app variable will still hold the destroyed vue instance (app wont be magically unset). So another execution of the code will not trigger the else block.
You also have to set app to something that evaluates to falsy.
if (app) {
app.$destroy();
app = undefined;
}
Now app is undefined so at the next time the if statment is evaluated, the else block would be evaluated and a new instance is created.
Your specific use case is not specified in the question, but I assume you want to update something that is not being updated automatically.
To update your component you can use
this.$forceUpdate()
to force a reload of the component.

Categories