VueJS Adding data from query parameters

3.5k views Asked by At

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
1

There are 1 answers

2
Miguel Coder On BEST ANSWER

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.