mounted method is fired before data loaded - VueJS - vue.js

I'm using Vue Resource to retrieve an images collection from a REST API. The request is sent in the created hook of my Vue component.
The problem is, I'm trying to access the retrieved data in the mounted hook, but the data isn't loaded.
I get this error in the console:
[Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'forEach' of undefined"
Here is my component:
<script>
export default {
data() {
return { imgs : '' };
},
created() {
// the full url is declare in my main.js
this.imgs = this.$resource('acf/v3/pages/4');
this.imgs.query().then((response) => {
console.log('success', response);
this.imgs = response.data.acf.gallery;
}, (response) => {
console.log('erreur', response);
});
},
mounted() {
// get the ref="image" in my dom template
let imgs = this.$refs.image;
imgs.forEach((img) => {
// I do some stuff with imgs
});
}
}
</script>
If I wrap a setTimeout around the content of mounted, everything works fine.
So, I don't understand how I can wait for my data to load before the mounted hook is executed. Isn't this the role of the Vue lifecycle hooks?

Since the this.imgs.query() call is async, your mounted hook is being called before the then handler is setting this.imgs (which I'm assuming is being bound with v-for to an element in your template with an attribute ref="image"). So, even though the component has been mounted to the DOM, the $refs have not been set up yet.
I would make a method to "do some stuff with imgs" and then call that method in a $nextTick callback in the then handler of the async call. The callback passed to $nextTick will be "executed after the next DOM update cycle", meaning the $refs will be set up at that point.
<script>
export default {
data() {
return { imgs: '' };
},
created() {
// the full url is declare in my main.js
this.imgs = this.$resource('acf/v3/pages/4');
this.imgs.query().then((response) => {
console.log('success', response);
this.imgs = response.data.acf.gallery;
this.$nextTick(() => this.doStuffWithImgs());
}, (response) => {
console.log('erreur', response);
});
},
methods: {
doStuffWithImgs() {
// get the ref="image" in my dom template
let imgs = this.$refs.image;
imgs.forEach((img) => {
// I do some stuff with imgs
});
}
}
}
</script>

As shown in the Lifecycle Diagram of Vue instance. After Mounted Hook (which means we can access DOM), there is also beforeUpdate and updated hooks. These hooks can be used when data is changed. I think beforeUpdate or update hook can be used after getting data in created hook.
<script>
export default {
data() {
return { imgs : '' };
},
created() {
// the full url is declare in my main.js
this.imgs = this.$resource('acf/v3/pages/4');
this.imgs.query().then((response) => {
console.log('success', response);
this.imgs = response.data.acf.gallery;
}, (response) => {
console.log('erreur', response);
});
},
// here we can use beforeUpdate or updated hook instead of mounted
beforeUpdate() {
// get the ref="image" in my dom template
let imgs = this.$refs.image;
imgs.forEach((img) => {
// I do some stuff with imgs
});
}
}
I hope this helps.

Related

The prop object's property is undefined in refresh function

I use Vue.js and have a component. I pass a prop "request" to that component:
<adjustments-list
v-if="request"
:request="request"
/>
In the component I'm able to do this:
<text-input
:value="request.id"
/>
It works that is the value of "id" is displayed.
In props section of component:
props: {
request: Object
In mounted hook of component:
async mounted () {
await this.refresh()
},
In refresh function of component:
async refresh () {
console.log('this.request.id =', this.request.id)
if (this.request.id) {
const data = await requestApi.getRequestResultAdjustmentByReqId(this.request.id)
}
},
The this.request.id is undefined.
I'm not sure why.
If the request property is asynchronously available to the component then, you have to use combination of watchers like:
// adjustments-list component
new Vue({
props: {
request: Object
},
data() {
return {
apiData: null
}
},
watch: {
request(newValue, _oldValue) {
this.refresh(newValue);
}
},
mounted: function () {
// Do something here
},
methods: {
refresh (request) {
if (request.id) {
// Using promise instead of async-await
requestApi.getRequestResultAdjustmentByReqId(request.id)
.then(() => this.apiData = data);
}
}
}
});
Also, note that, mounted should be a plain old JS function and not an async function. That's the lifecycle method of the component supposed to behave in particular way.

VueJs Data Passed From Root to Child Component via Prop Results in only an observable object

I have an app which calls a web service in the created() function and populates a property of the root data object. The property is passed via a prop to a child component and using the Chrome dev tools I can see that the prop data is available on the child component.
The problem I have is that I try to set data properties in the child component using values passed via the prop I end up with undefined property data. If I use the Chrome inspection tools and add a breakpoint I can see that the prop is an observable object in the form of {__ob__: Observer} and as such, I cannot directly access any of the data. My suspicion is that the child object sets it's data properties before the web service call has completed in the root.
How can I overcome this?
I've created a JsFiddle for this:
https://jsfiddle.net/ProNotion/a8c6nqsg/
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {
customerEmail: this.customer_data.customerEmail1
}
}
}
});
new Vue({
el: "#app",
data() {
return {
customer: {}
};
},
methods: {
init() {
var self = this;
axios.get("https://0bb1313e-b089-432e-b6bc-250f6162d7f0.mock.pstmn.io/GetCustomerData")
.then(response => {
self.customer = response.data;
}).catch(response => {
console.error(response);
});
}
},
created() {
this.init();
}
});
Here is my HTML markup:
<div id="app">
<mycomponent :customer_data="customer" />
</div>
<script type="x-template" id="my-component-template">
<div>
<p>{{form_data.customerEmail1}}</p>
</div>
</script>
Check response data type and format
console.log(typeof response.data) // string
{ "customerEmail1": "me#example.com", } // Remove `,`
You must parse to JSON type
axios.get(...).then(response => {
self.customer = JSON.parse(response.data.replace(',', ''))
})
Set property to watch with `deep` option
[Deep watching](https://v2.vuejs.org/v2/api/#vm-watch) will be detect nested value changes inside Objects
```
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {}
}
},
watch: {
customer_data: {
handler (val) {
this.form_data = val;
},
deep: true
}
}
});
```
Demo: https://jsfiddle.net/ghlee/f4gewvqn

ag grid not retrieving data when mounted with vue using axios

I have this strange case when trying to retrieve data from mongoDB using axios not showing on grid. It should be already successful given the data can already loaded into the view (already tested it), but it's nowhere inside beforeMount, mounted, or ready hook.
I already tried with
this.gridOptions.onGridReady = () => {
this.gridOptions.api.setRowData(this.ticketData)
}
but only yields partial success (unreliable),
here's a code snippet to show what I mean,
<template>
<div class="ticketing">
<ag-grid-vue style="width: 100%; height: 350px;"
class="ag-fresh"
:gridOptions="gridOptions"
>
</ag-grid-vue>
{{testData}} <!--testData can be loaded-->
<input type="button" #click.prevent="showData" value="test"> </div>
</template>
<script>
//import stuff
//header and url stuff
export default {
//component stuff
data () {
return {
gridOptions: null,
ticketData: [],
testData: [] // only for testing purpose
}
},
methods: {
showData () {
console.log('data shown')
this.testData = this.ticketData // this is working
}
},
beforeMount () {
var vm = this
axios.get(ticketingAPIURL, {'headers': {'Authorization': authHeader}})
.then(function (response) {
vm.ticketData = response.data
}) // this is working
.catch(function (error) {
console.log(error)
})
this.gridOptions = {}
this.gridOptions.rowData = this.ticketData // this is not working
this.gridOptions.columnDefs = DummyData.columnDefs
}
// mount, ready also not working
}
</script>
To be more specific, I still can't determine what really triggers onGridReady of ag-grid in conjunction with Vue component lifecycle, or in other words, how can I replace button to show testData above with reliable onGridReady/Vue component lifecycle event?
You define vm.ticketData and after you call it like this.ticketData
You can change it by: this.rowData = vm.ticketData
You are setting this.gridOptions.rowData outside of the axios callback, so this.ticketData is still empty.
Set it inside the callback:
mounted() {
var vm = this
axios.get(ticketingAPIURL, {'headers': {'Authorization': authHeader}})
.then(function (response) {
vm.ticketData = response.data
vm.gridOptions = {}
vm.gridOptions.rowData = vm.ticketData
vm.gridOptions.columnDefs = DummyData.columnDefs
})
.catch(function (error) {
console.log(error)
})
}
it is due to overlapped intialization between axios, ag-grid, and vue.
after much tinkering, I am able to solve it with using Vue's watch function:
watch: {
isAxiosReady(val) {
if (val) {
this.mountGrid() // initiate gridOptions.api functions
}
}
}

VueJS - Accessing store data inside mounted

I'm having trouble understanding the following:
I have a store which contains variables needed for the application. In particular, there is a globalCompanies which stores:
globalCompanies: {
current: [],
all: [],
currentName: "",
}
Inside another component, I want to do the following:
mounted() {
this.$store.dispatch( "fetchUsers" );
var currentName = this.$store.state.globalCompanies.currentName;
console.log(currentName);
},
However, this just shows as empty. I know the value is there because I have computed which returns the currentName and it works fine inside the view itself. It just doesn't like the fact that it's in the mounted component.
Where am I going wrong and what can I do to resolve this issue? I really need to capture the companies Name in order to use it for some real time events.
As a result of our discussion:
In the question Vuex state value, accessed in component's mounted hook, returns empty value, because it is set in an async action which does not resolve before mounted executes.
When you need to trigger some function when async action in Vuex resolves with a value, you can achieve it using watch on a computed property, which returns a value from your Vuex state. When a value in store changes, the computed property reflects these changes and watch listener executes:
const store = new Vuex.Store({
state: {
globalCompanies: {
test: null
}
},
mutations: {
setMe: (state, payload) => {
state.globalCompanies.test = payload
}
},
actions: {
pretendFetch: ({commit}) => {
setTimeout(() => {
commit('setMe', 'My text is here!')
}, 300)
}
}
})
new Vue({
el: '#app',
store,
computed: {
cp: function() { // computed property will be updated when async call resolves
return this.$store.state.globalCompanies.test;
}
},
watch: { // watch changes here
cp: function(newValue, oldValue) {
// apply your logic here, e.g. invoke your listener function
console.log('was: ', oldValue, ' now: ', newValue)
}
},
mounted() {
this.$store.dispatch('pretendFetch');
// console.log(this.cp, this.$store.state.globalCompanies.test); // null
// var cn = this.$store.state.globalCompanies.test; // null
// console.log(cn) // null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
{{ cp }}
</div>
VueJS - Accessing Store Data Inside Mounted
Ran into this issue and it turned out to be a scope issue.
Store:
export default () => {
items:[],
globalCompanies:{
current:[],
all:[],
currentName: "Something"
},
ok: "Here you go"
}
Getters:
export default {
getGlobalCompanies(state){
return state.globalCompanies;
}
}
Mounted: This works...
mounted() {
// Initialize inside mounted to ensure store is within scope
const { getters } = this.$store;
const thisWorks = () => {
const globalCompanies = getters.getGlobalCompanies;
}
},
This is Bad: Reaching for the store outside the mounted scope
mounted() {
function ThisDontWork() {
const { getters } = this.$store; // this.$store == undefined
}
ThisDontWork();
},

Vuex rendering data that is fetched from REST API

For such component
<template>
<div>
<router-link :to="{name:'section', params: { sectionId: firstSectionId }}">Start</router-link>
</div>
</template>
<script lang="ts">
import { mapActions } from "vuex"
export default {
mounted() {
this.getSectionId()
},
computed: {
firstSectionId() {
return this.$store.state.firstSectionId
}
},
methods: mapActions(["getSectionId"])
}
</script>
Store:
const store: any = new Vuex.Store({
state: {
firstSectionId: null
},
// actions,
// mutations
})
I have a web request in the getSectionId action and it asynchronously fetches data and calls a mutation that will fill firstSectionId in state. During the initial rendering firstSectionId is null and I get the warning that a required parameter is missing during rendering of router-link.
It is not a problem here to add v-if="firstSectionId". But in general what is the approach for fetching data from a server to be displayed? Currently all my components are checking if there is data present in the store before rendering, is it normal or is there a better way to wait for data to be loaded before rendering it?
One approach for asynchronously fetching data is to use promise in vuex store actions.
Vue.http.get(API_URL)
.then((response) => {
//use response object
})
.catch((error) => {
console.log(error.statusText)
});
To demonstrate that I make request to this route. You can see how response should looks like. Let's save response object in state.users array.
store.js
const store = new Vuex.Store({
state: {
users: []
},
mutations: {
FETCH_USERS(state, users) {
state.users = users
}
},
actions: {
fetchUsers({ commit }, { self }) {
Vue.http.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
commit("FETCH_USERS", response.body);
self.filterUsers();
})
.catch((error) => {
console.log(error.statusText)
});
}
}
})
export default store
You noticed that there is self.filteruser() method after commit. That is crucial moment. Before that we are committing a mutation, which is synchronous operation and we are sure that we will have our response in store.state that can be used in filterUsers() method (don't forget to pass self parm)
Users.vue
import store from "../store/store"
export default {
name: 'users',
created() {
this.$store.dispatch("fetchUsers", { self: this })
},
methods:{
filterUsers() {
//do something with users
console.log("Users--->",this.$store.state.users)
}
}
}
Better ways (ES6 & ES7)
ES6 Promises for asynchronous programming
//User.vue
created() {
this.$store.dispatch("fetchUser").then(() => {
console.log("This would be printed after dispatch!!")
})
}
//store.js
actions: {
fetchUser({ commit }) {
return new Promise((resolve, reject) => {
Vue.http.get("https://jsonplaceholder.typicode.com/users")
.then((response) => {
commit("FETCH_USERS", response.body);
resolve();
})
.catch((error) => {
console.log(error.statusText);
});
});
}
}
ES7: async/await
To get away from callback hell, and to improve asynchronous programming use async function, and you can await on a promise. Code looks much easier to follow (like it is synchronous), but code isn't readable for browsers so you'll need Babel transpiler to run it.
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // wait for actionA to finish
commit('gotOtherData', await getOtherData())
}
}
In my experience, you can skip a few checks if you preset the state with an empty value of the same type as the expected result (if you know what to expect, of course), e.g. if you have an array of items, start with [] instead of null as it won't break v-for directives, .length checks and similar data access attempts.
But generally, adding v-if is a very normal thing to do. There's a section about this in the vue-router documentation and checking whether properties exist or not is exactly what it suggests. Another possible solution it mentions is fetching data inside beforeRouteEnter guard, which assures you will always get to the component with your data already available.
Ultimately, both solutions are correct, and the decision between them is more of a UX/UI question.
I had similar requirements for locations and the google map api. I needed to fetch my locations from the API, load them in a list, and then use those in a map component to create the markers. I fetched the data in a Vuex action with axios, loaded that in my state with a mutation, and then used a getter to retrieve the resulting array in the mounted life cycle hook. This resulted in an empty array as mounted fired before the async action resolved.
I used store.subscribe to solve it this way:
<template>
<div class="google-map" :id="mapName"></div>
</template>
<script>
import GoogleMapsLoader from 'google-maps';
import { mapGetters } from 'vuex';
export default {
name: 'google-map',
props: ['name'],
computed: {
...mapGetters({
locations: 'locations/locations',
}),
},
data() {
return {
mapName: `${this.name}-map`,
};
},
mounted() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'locations/SAVE_LOCATIONS') {
GoogleMapsLoader.KEY = 'myKey';
GoogleMapsLoader.load((google) => {
/* eslint-disable no-new */
const map = new google.maps.Map(document.getElementById('locations-map'));
// loop through locations and add markers to map and set map boundaries
const bounds = new google.maps.LatLngBounds();
// I access the resulting locations array via state.module.property
state.locations.locations.forEach((location) => {
new google.maps.Marker({
position: {
lat: location.latitude,
lng: location.longitude,
},
map,
});
bounds.extend({
lat: location.latitude,
lng: location.longitude,
});
});
map.fitBounds(bounds);
});
}
});
},
};