Is there any solution for tricking vue's lifecycle hook order of execution? - vue.js

Destroyed hook is called later than i need.
I tried to use beforeDestroy instead of destroy, mounted hook instead of created. The destroy hook of previous components is always called after the created hook of the components that replaces it.
App.vue
<div id="app">
<component :is="currentComponent"></component>
<button #click="toggleComponent">Toggle component</button>
</div>
</template>
<script>
import A from './components/A.vue';
import B from './components/B.vue';
export default {
components: {
A,
B
},
data: function(){
return {
currentComponent: 'A'
}
},
methods: {
toggleComponent() {
this.currentComponent = this.currentComponent === 'A' ? 'B' : 'A';
}
}
}
</script>
A.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from A');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
B.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from B');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
Result:
// Click Enter
Enter pressed from A
// now click on toggle component button
// Click Enter again
Enter pressed from A
Expected after the second enter to show me Enter pressed from B.
Please don't show me diagrams with vue's lifecycle, i'm already aware of that, I just need the workaround for this specific case.
Dumb answers like use setTimeout are not accepted.
EDIT: Made some changes to code and description

If you are using vue-router you can use router guards in the component (as well as in the router file) where you have beforeRouteLeave obviously only works where there is a change in route, see here:
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

Related

How do I access programmatically created refs in vue.js?

I would like to access refs in a vue.js component, where the ref itself is created dynamically like so:
<style>
</style>
<template>
<div>
<lmap class="map" v-for="m in [1, 2, 3]" :ref="'map' + m"></lmap>
</div>
</template>
<script>
module.exports = {
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
console.log('all refs', this.$refs);
// prints an object with 3 keys: map1, map2, map3
console.log('all ref keys', Object.keys(this.$refs));
// would expect ["map1", "map2", "map3"], prints an empty array instead
Vue.nextTick().then(() => {
console.log('map1', this.$refs["map1"]);
// would expect a DOM element, instead prints undefined
})
},
destroyed: function(){
},
methods: {
},
}
</script>
However this seems not to work (see above in the comments), and I can't figure why.
I think the problem is that you are importing the component asynchronously, with httpVueLoader, which then downloads and imports the component only when the component is rendered from the dom, therefore, the component has not yet been imported into the nextTick callback.
I suggest you put a loaded event in the map.vue component, maybe in mounted lifecycle , which will be listened to in the father, example #loaded = "showRefs"
surely when the showRefs(){ } method is invoked, you will have your refs populated ;)
Try using a template string e.g
`map${m}`
You have to wait until components have been rendered / updated. This works:
module.exports = {
data: function () {
return {
};
},
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
},
destroyed: function(){
},
updated: function(){
Vue.nextTick().then(() => {
console.log('all ref keys', Object.keys(this.$refs));
console.log('map1', this.$refs['map1'][0].$el);
})
},
methods: {
},
}

How to dynamically mount vue component with props

Scenario / context
I have an overview component which contains a table and an add button. The add button opens a modal component. When i fill in some text fields in the modal and click the save button, a callback (given as prop) is called so the parent component (the overview) is updated. The save button also triggers the model toggle function so the model closes.
So far works everything like expected but when i want to add a second entry, the modal is "pre-filled" with the data of the recently added item.
Its clear to me that this happens because the model component keeps mounted in the background (so its just hidden). I could solve this by "reset" the modals data when the toggle function is triggered but i think there should be a better way.
I have a similar issue when i want to fetch data in a modal. Currently i call the fetch function in the mounted hook of the modal. So in this case the fetch happens when the parent component mounts the modal. This does not make sense as it should only (and each time) fetch when the modal is opened.
I think the nicest way to solve this is to mount the modal component dynamically when i click the "add" (open modal) button but i can't find how i can achieve this. This also avoids that a lot of components are mounted in the background which are possibly not used.
Screenshot
Example code
Overview:
<template>
<div>
// mount of my modal component
<example-modal
:toggleConstant = modalToggleUuid
:submitHandler = submitHandler />
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
someList: [],
}
},
mounted() {
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Modal:
<template>
<div>
<input v-model="item.type">
<input v-model="item.name">
<input v-model="item.location">
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
item: {},
}
},
mounted() {
// in some cases i fetch something here. The data should be fetched each time the modal is opened
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Question
What is the best practive to deal with the above described scenario?
Should i mount the modal component dynamically?
Do i mount the component correctly and should i reset the content all the time?
You are on the right way and in order to achieve what you want, you can approach this issue with v-if solution like this - then mounted() hook will run every time when you toggle modal and it also will not be present in DOM when you are not using it.
<template>
<div>
// mount of my modal component
<example-modal
v-if="isShowModal"
:toggleConstant="modalToggleUuid"
:submitHandler="submitHandler"
/>
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
isShowModal: false,
modalToggleUuid: someUuid,
someList: []
};
},
mounted() {},
methods: {
showModal: function() {
this.isShowModal = true;
},
submitHandler: function(item) {
this.someList.push(item);
this.isShowModal = false;
}
}
};
</script>

How to open vuetify dialog after user logs in to application

In my application I want to show a modal to introduce the user in my application, so it will appear only in the first time he logs in. What I am doing is storing isNewUser in the global state and using it to know if it should render the modal or not using the same process described in this answer. (I'm not using event bus)
Here is my parent component:
<template>
<Intro :value="isNewUser" #input="finishTutorial" />
</template>
mounted() {
const store = this.$store;
this.isNewUser = store.state.auth.user.isNewUser;
},
When the user logs in and this component is rendered I saw the dialog being rendered and closing. If I hit f5 it reloads the page and dialog is showed correctly.
If I do the bellow modification it works, but I don't want to solve the problem this way since it won't work for all cases, it will depend on the speed of the user computer/internet.
mounted() {
setTimeout(() => {
const store = this.$store;
this.isNewUser = store.state.auth.user.isNewUser;
}, 2000);
},
I've tried using v-if as well
<template>
<Intro v-if="isNewUser" :value="true" #input="finishTutorial" />
</template>
<script>
export default {
components: {
Intro,
},
data() {
return {
isNewUser: false,
};
},
mounted() {
const store = this.$store;
this.isNewUser = store.state.auth.user.isNewUser;
},
methods: {
async finishTutorial() {
this.$store.dispatch('auth/finishTutorial');
this.isNewUser = false;
},
},
};
</script>
You can use a computed property to do so:
computed: {
isNewUser() {
return this.$store.state.auth.user.isNewUser;
}
}
and in the template you would do like so:
<template>
<Intro :value="isNewUser" #input="finishTutorial" />
</template>

Reload navbar component on every this.$router.push() call

I developing a login/registration system in my Vue.js app. I want the items in navbar to be updated when I call this.$router.push('/').
App.vue:
<template>
<div id="app">
<Navbar></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Navbar component:
export default {
name: "Navbar",
data: function() {
return {
isLoggedIn: false,
currentUser: null
}
},
methods: {
getAuthInfo: function() {
this.isLoggedIn = this.auth.isLoggedIn();
if (this.isLoggedIn) {
this.currentUser = this.auth.currentUser();
}
}
},
mounted: function() {
this.getAuthInfo();
},
updated: function() {
this.getAuthInfo();
}
}
Here is how I redirect to another page:
const self = this;
this.axios
.post('/login', formData)
.then(function(data) {
self.auth.saveToken(data.data.token);
self.$router.push('/');
})
.catch(function(error) {
console.log(error);
self.errorMessage = 'Error!';
});
SUMMARY: The problem is that isLoggedIn and currentUser in Navbar don't get updated when I call self.$router.push('/');. This means that functions mounted and updated don't get called. They are updated only after I manually refresh the page.
I solved the problem with adding :key="$route.fullPath" to Navbar component:
<template>
<div id="app">
<Navbar :key="$route.fullPath"></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Check this out from the docs:
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
},
I expect your Navbar component is reused across routes so its mounted and updated are not called. Try using beforeRouteUpdate if you want to do some processing on route change.

Hiding Element Based on Route Path or Params in Vue

I'm trying to hide the main app navigation bar based on if the route is on a given path.
In my App.vue component, in the created() method. I do check to see if the route is x || y, if either of those are true, I set my Vuex state of show to false. If it is any other route besides those two, I set show = true.
Then in my template I do this
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
I'm noticing in Vuex tools that my mutations aren't even registering so I'm not sure why that is. Do they need to be actions instead? Here is my full code.
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
<script>
import Navigation from './components/Navigation/Navigation'
import { firebaseAuth } from './firebase/constants'
import store from './store/index'
export default {
name: 'app',
components: {
Navigation
},
computed: {
show () {
return store.state.navigation.show
}
},
created() {
// Checks for a user and dispatches an action changing isAuthed state to true.
firebaseAuth.onAuthStateChanged(user => {
console.log(store.state.authentication);
console.log(user);
store.dispatch('checkUser', user);
});
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/dashboard/products" || this.$route.path === "/dashboard/settings") {
store.commit('hideNav');
} else if (this.$route.path !== "/dashboard/products" || this.$route.path !== "/dashboard/settings") {
store.commit('showNav');
}
}
};
</script>
This may not be working as created is called only once after the instance is created. but when routes changes, it will not be called, so not triggering the mutations you are expecting to trigger on route change, instead of this, you can put a watch on route, so on each route change, you can check whether to show your Nav Bar or not, like following;
Working fiddle: http://jsfiddle.net/ofr8d85p/
watch: {
$route: function() {
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/user/foo/posts") {
store.commit('SHOWNAV');
} else {
store.commit('HIDENAV');
}
}
},