Mounted hook running before Created data api is finished loading - vue.js

I am trying to load a function when images from a data api are finished loading. However, it looks like the function is run before the ApiService is finished and thus the TiffParser.replaceIMG() function is not working properly
Here's my setup:
data: function() {
return {
images: null,
imageLink: apiService.imgSrc,
loading: true,
errored: false
};
},
created: function() {
// fetch the data when the view is created and the data is
// already being observed
apiService
.getImages(this.$route.params.id)
.catch(error => {
console.log(error);
this.errored = true;
})
.then(response => {
this.loading = false;
this.images = response.data;
});
},
//vue js provides us `mounted()`. This means `onload` in javascript
mounted: function() {
TiffParser.replaceIMG();
}
Is mounted the correct lifecycle hook for this task?

You can create a watcher for your images.
created() {
const unwatch = this.$watch('images', function(newValue = [], oldValue = []) {
// any code here will execulte once the value of `images` changes
TiffParser.replaceIMG();
unwatch(); // remove the watcher
// Note that you cannot use ES6 arrow functions here, since arrow functions
// are bound to the parent context, and the `this` keyword
// would then not be bound correctly to the Vue instance.
});
// fetch images
}
Is mounted the correct lifecycle hook for this task?
Yes, if you need to access or modify the DOM of your component immediately before or after the initial render.
However, images would be empty when it's first mounted so using a watcher instead of the mounted hook seems more appropriate for this use case.

Related

How to call function when update state and after DOM full loaded

I want when update state and after DOM full loaded, I will use js to update CSS. So now, I'm using document ready function in the method. Is there any writing style in Vuex? How can I write them in mounted?
computed: {
...mapGetters([
'wsInfo'
])
},
mounted () {
??????
},
method: {
moveWs (from, to) {
//update state
this.wsInfo.workspaces.splice(to, 0, this.wsInfo.workspaces.splice(from, 1)[0])
$(document).ready(function () {
// code run after update state and dom loaded
})
}
}
You need to use a nextTick Function.
Also you can use it inside a method vuejs object.
You can read more about it here:
https://v2.vuejs.org/v2/api/#vm-nextTick
new Vue({
// ...
methods: {
// ...
example: function () {
// modify data
this.message = 'changed'
// DOM is not updated yet
this.$nextTick(function () {
// DOM is now updated
// `this` is bound to the current instance
this.doSomethingElse()
})
}
}
})

How to use async/await in vue lifecycle hooks with vuex?

When I dispatch an action in App.vue component in mounted() lifecycle hook, it runs after other components load. I am using async/await in my action and mounted lifecycle hook.
App.vue file
methods: {
...mapActions({
setUsers: "setUsers",
}),
},
async mounted() {
try {
await this.setUsers();
} catch (error) {
if (error) {
console.log(error);
}
}
},
action.js file:
async setUsers(context) {
try {
const response = await axios.get('/get-users');
console.log('setting users');
if (response.data.success) {
context.commit('setUsers', {
data: response.data.data,
});
}
} catch (error) {
if (error) {
throw error;
}
}
},
In Users list component, I need to get users from vuex. So I am using mapGetters to get Users list.
...mapGetters({
getUsers: "getUsers",
}),
mounted() {
console.log(this.getUsers);
},
But the problem is "setting users" console log in running after console logging the this.getUsers.
In Users list component, I can use getUsers in the template but when I try to console log this.getUsers it gives nothing.
How can I run app.vue file before running any other components?
You are using async await correctly in your components. It's important to understand that async await does not hold off the execution of your component, and your component will still render and go through the different lifecycle hooks such as mounted.
What async await does is hold off the execution of the current context, if you're using it inside a function, the code after the await will happen after the promise resolves, and in your case you're using it in the created lifecycle hook, which means that the code inside the mounted lifecycle hook which is a function, will get resolved after the await.
So what you want to do, is to make sure you render a component only when data is received.
Here's how to do it:
If the component is a child component of the parent, you can use v-if, then when the data comes set data to true, like this:
data() {
return {
hasData: false,
}
}
async mounted() {
const users = await fetchUsers()
this.hasData = true;
}
<SomeComponent v-if="hasData" />
If the component is not a child of the parent, you can use a watcher to let you know when the component has rendered. When using watch you can to be careful because it will happen every time a change happens.
A simple rule of thumb is to use watch with variables that don't change often, if the data you're getting is mostly read only you can use the data, if not you can add a property to Vuex such as loadingUsers.
Here's an example of how to do this:
data: {
return {
hasData: false,
}
},
computed: {
isLoading() {
return this.$store.state.app.users;
}
},
watch: {
isLoading(isLoading) {
if (!isLoading) {
this.hasData = true;
}
}
}
<SomeComponent v-if="hasData" />
if you're fetching a data from an API, then it is better to dispatch the action inside of created where the DOM is not yet rendered but you can still use "this" instead of mounted. Here is an example if you're working with Vuex modules:
created() {
this.fetchUsers();
},
methods: {
async fetchUsers() {
await this.$store.dispatch('user/setUsers');
},
},
computed: {
usersGetters() {
// getters here
},
},
Question: Do you expect to run await this.setUsers(); every time when the app is loaded (no matter which page/component is being shown)?
If so, then your App.vue is fine. And in your 'Users list component' it's also fine to use mapGetters to get the values (note it should be in computed). The problem is that you should 'wait' for the setUsers action to complete first, so that your getUsers in the component can have value.
A easy way to fix this is using Conditional Rendering and only renders component when getUsers is defined. Possibly you can add a v-if to your parent component of 'Users list component' and only loads it when v-if="getUsers" is true. Then your mounted logic would also work fine (as the data is already there).

When passing data from parent component to child component via props, the data appears to be undefined in the mounted hook of the child component

In my parent component:
<UsersList :current-room="current_room" />
In the child component:
export default {
props: {
currentRoom: Object
},
data () {
return {
users: []
}
},
mounted () {
this.$nextTick( async () => {
console.log(this.currentRoom) // this, weirdly, has the data I expect, and id is set to 1
let url = `${process.env.VUE_APP_API_URL}/chat_room/${this.currentRoom.id}/users`
console.log(url) // the result: /api/chat_room/undefined/users
let response = await this.axios.get(url)
this.users = response.data
})
},
}
When I look at the page using vue-devtools, I can see the data appears:
I've run into this issue in the past – as have many others. For whatever reason, you can't rely on props being available in the component's mounted handler. I think it has to do with the point at which mounted() is called within Vue's lifecycle.
I solved my problem by watching the prop and moving my logic from mounted to the watch handler. In your case, you could watch the currentRoom property, and make your api call in the handler:
export default {
props: {
currentRoom: Object
},
data() {
return {
users: []
}
},
watch: {
currentRoom(room) {
this.$nextTick(async() => {
let url = `${process.env.VUE_APP_API_URL}/chat_room/${room.id}/users`
let response = await this.axios.get(url)
this.users = response.data
})
}
},
}
I don't think you really need to use $nextTick() here, but I left it as you had it. You could try taking that out to simplify the code.
By the way, the reason console.log(this.currentRoom); shows you the room ID is because when you pass an object to console.log(), it binds to that object until it is read. So even though the room ID is not available when console.log() is called, it becomes available before you see the result in the console.

Which Lifecycle hook after axios get but before DOM render

I'm trying to render my DOM, dependent on some data I'm returning from an axios get. I can't seem to get the timing right. The get is in the created hook, but there is a delay between the get and actually receiving the data. Basically if there is info in seller_id then I need to show the cancel button, otherwise don't. Here is my code:
this is in my created hook
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
and then this is the logic to show or hide the button. I've tried created, mounted, beforeUpdate, and updated all with no luck. I've also tried $nextTick but I can't get the timing correct. This is what I have currently:
beforeUpdate: function () {
// this.$nextTick(function () {
function sellerIdNotBlank() {
var valid = this.seller_id == '';
return !valid;
}
if(sellerIdNotBlank()){
this.show_cancel_button = true;
}
// })
},
First, it is pointless to get your data from backend and try to sync with Vue.js lifecycle methods. It never works.
Also, you should avoid beforeUpdate lifecycle event. It is often a code smell. beforeUpdate is to be used only when you have some DOM manipulations done manually and you need to adjust them again before Vue.js attempt to re-render.
Further, show_cancel_button is a very good candidate for a computed property. Here is how component will look:
const componentOpts = {
data() {
return {
seller_id: '',
// ... some more fields
};
},
created() {
axios.get('https://bc-ship.c9users.io/return_credentials').then(response => {
this.seller_id = response.data.seller_id;
this.selected_marketplace = response.data.marketplace;
this.token = response.data.auth_token;
});
},
computed: {
show_cancel_button() {
return this.seller_id !== '';
}
}
}

Show loading spinner for async Vue 2 components

I have a fairly heavy component which I would like to load asynchronously, while at the same time showing the user a loading spinner when it's loading.
This is my first attempt, using loading defined in data linked to a spinner component with v-if="loading". Unfortunately this doesn't work because it seems that Vue doesn't rebind this properly for functions inside components -
export default {
data: {
return {
loading: false,
};
},
components: {
// ...
ExampleComponent: (resolve) => {
// Doesn't work - 'this' is undefined here
this.loading = true;
require(['./ExampleComponent'], (component) => {
this.loading = false;
resolve(component);
});
},
},
};
I've also found some Vue 1.0 examples, but they depended on $refs - in 2.0 $refs is no longer reactive, and cannot be used for this. The only way left is for the child component itself to do something on its mount lifecycle event to the application data state to remove the loading spinner, but that seems a bit heavy. Is there a better way to do this?
You could declare a variable outside the object scope (but still is within module scope) then use the created hook to attach this. So your updated code would look like:
let vm = {}
export default {
// add this hook
created () {
vm = this;
},
data: {
return {
loading: false,
};
},
components: {
// ...
ExampleComponent: (resolve) => {
// since 'this' doesn't work, we reference outside 'vm'
vm.loading = true;
require(['./ExampleComponent'], (component) => {
vm.loading = false;
resolve(component);
});
},
},
};