I am working on a small purely client-side VueJS application that uses VueRouter
to update the URL when data is entered into the application, so users can use the URL to refresh the page and reload their data into the app when refreshing the page. The data is stored in query parameters, ex.:
.../app.html#/?time=300&time=300&distance=300&distance=300&unit=m&unit=m
The data is parsed as needed by the router
, but when I add the data to my empty performance
array, the :class
attribute in my markup raises a
TypeError: Cannot read property 'length' of undefined
Where undefined
is the performances
array. I have narrowed this issue down to the :class
attribute inside of the v-for
performance loop by removing the :class
from the app's markup and successfully rendered the site. I am guessing that the performances
data array has not been passed to the :class
.
I did not see any mention of this type of routing in the VueRouter documentation. How should data be passed from the URL query parameters into the application? Here is my relevant code (condensed for the sake of brevity):
// js file
var router = new VueRouter({}); // empty because no routes, just query parameters, so no need for list
var app = new Vue({
el: '#app',
data: function() {
return {
performances: [],
units: units,
newPerformance: {
distance: {
value: '',
units: units.distance.options[0]
},
time: {
formatted: '',
value: ''
},
},
editingPerformance: null,
}
},
router: router,
watch: {
performances: {
handler: function() {
this.drawChart();
this.updateURL();
},
deep: true
}
},
updateURL: function() {
var times = this.performances.map(function(v) {
return v.time.value
}),
distances = this.performances.map(function(v) {
return v.distance.value
}),
units = this.performances.map(function(v) {
return v.distance.units.value
});
router.push({
query: {
time: times,
distance: distances,
dunit: units
}
});
}
....
});
HTML file
...
<div v-for="(performance, index) in performances" :class="{'card-list': performances.length > 1 }">
...
</div>
...
I have read the Reactivity in Depth documentation and tried such solutions as:
Vue.nextTick(function() {
this.performances.push(performance);
})
and
Vue.set(app, 'performances',performanceArray);
which result in the same error. I can provide more code upon request.
Edit: Adding stack traces:
[Vue warn]: Error when rendering root instance: warn @ vue.js:513
Vue._render @ vue.js:2934
(anonymous function) @ vue.js:2335
get @ vue.js:1643run @ vue.js:1712
flushSchedulerQueue @ vue.js:1530
(anonymous function) @ vue.js:465
nextTickHandler @ vue.js:414
followed immediately by:
vue.js:427 TypeError: Cannot read property 'length' of undefined(…)
logError @ vue.js:427
It seems like you might be setting it to something other than an array somewhere in your code. You instantiated it as an array, and .length only works on arrays and strings so if you set it to an object, json, or anything else it will error out. Vue.nextTick has a callback that won't carry "this" over into it unless you set it to a variable in the same scope as your Vue.nextTick function. something like
var self = this;
//inside nextTick
self.performances.push(performance);
but i would imagine that you shouldn't have to use nextTick because nextTick updates after the DOM has been re-rendered. the push() function will trigger a dom re-render, which will make your page responsive. If you wait for nextTick then it may never update until you change something else.
<div v-if="performances.length" v-for="(performance, index) in performances"
:class="performances.length > 1 ? 'card-list' : ''">
...
</div>
The v-if attribute will make sure that it doesn't render until the content is there.
That should fix your problem
You can also do {{ performance }} to find out what is exactly in the object, and Google chrome has a vue-debugger extension. It shows you a live model of all of your data properties. I would recommend using it.