Yasmin French
Yasmin French

Reputation: 1282

Vuejs page loads before axios request returns data

Problem

Im trying to load data from my database using vuejs + axios in a Laravel project. Locally I can make the request using axios and it returns the data, assigns it to variables and then outputs the data to the DOM.

When I push the same code to production the axios request takes a little longer to execute but it seems to be long enough that vuejs is ready before all the data is returned, leaving me with an annoying 'undefined property' error when the data attempts to be displayed. Sometimes this error persist's through 20+ refreshes before the data loads.

What I think the issue is

So my guess from reading up is that axios isn't getting the data as quickly as it should do so vuejs is ready to start serving the data but axios hasn't had a chance to collect it yet, resulting in the undefined error

What i've read and tried

I've read that using v-if statements on the objects that depend on the data should fix this issue. But it doesn't, all it does it hide the object from view if the data is not present. For Example...

HTML

<!-- If user.name exists, display user.name -->
<div v-if="user.name">
    @{{ user.name }}
</div>

JAVASCRIPT

<script>
    new Vue({
        el: '#container',
        data: {
            user: {}
        },
        mounted() {
            this.getUserData();
        },
        methods: {
            getUserData(){
                axios.post('/user/get').then((response) => {

                    // Check the response was a success
                    if(response.data.status === 'success')
                    {
                        this.user = response.data.user;
                    }
                });
            },
        }
    })
</script>

This doesn't work, it just doesn't display anything when the data hasn't been loaded yet.

Question

How do I ensure that my loaded data is displayed and not return an undefined error? The only way I can think is by allowing the user to 'click to retrieve data' on a failed attempt.

Further Information

Im not using vue templates, the child/parent structure or any vue libraries. I'm importing vue.js via CDN and using it on the page in a script as seen above. Im not sure if using it like this will cause any such limitations, i've only learnt vue.js on a basic and this works for the company and the boss...

Upvotes: 5

Views: 11007

Answers (4)

<div v-if="user">
   <div>
       @{{ user.name }}
   </div>
   <div>
       @{{ user.second_name }}
   </div>
   <div>
       @{{ user.phone }}
   </div>
   <div>
       @{{ user.any }}
   </div>
</div>

Upvotes: 0

Peter van der Lely
Peter van der Lely

Reputation: 109

With the help of lodash's debounce you can delay the time between when a method function or computed property is called and excecuted. Also you can differentiate the delay time for the same method creating different functions eg. one being triggered by mounted and the other one by a watcher. In my case the function being called on mounted, needs to wait before the axios call returns a response and sets store.state.TableDataInit. In the watcher case it can run immediately because the store.state variable already has been set, there is no need to wait for an axios response anymore. For example:

created: function() {
    this.setInitRange = _.debounce(this.setRange, 600);
    this.setInitBoundaries = _.debounce(this.setBoundaries, 600);
    this.setWatchRange = this.setRange;
    this.setWatchBoundaries = this.setBoundaries;
},
mounted() {    
    this.setInitRange(this.selected);
    this.setInitBoundaries();
},

watch: {
    selected() {      
      store.commit("storedMetric", this.selected);
      this.setWatchRange(this.selected); 
      this.setWatchBoundaries();
    }    
},
methods: {
    setRange(value) {
      var metric = value;
      var dataArray = store.state.TableDataInit; 
      const maxMetric = dataArray.reduce(function(prev, current) {
        return _.get(prev, metric) > _.get(current, metric) ? prev : current;
      })[metric];
      const minMetric = dataArray.reduce(function(prev, current) {
        return _.get(prev, metric) < _.get(current, metric) ? prev : current;
      })[metric];
      this.range = [minMetric, maxMetric];
      store.commit("storedOutBoundRange", this.range);
   },
   setBoundaries() {
      this.min = store.state.OutBoundRange[0];
      this.max = store.state.OutBoundRange[1];
    },

Upvotes: 0

Bergur
Bergur

Reputation: 4067

Edited answer, to explain better what I mean.

Your code should work, no matter if it takes 1 sec or 10 sec for axios to fetch the data. You shouldn't have to check with v-if on user.name. VueJS has reactivity and upates the view if the data property changes.

See this code example. https://codepen.io/bergur/pen/VOXrzR

Here I wait 10 seconds to populate the user object

  1. {{ user.name }} shows nothing and then Bergur
  2. {{ computedName }} shows undefined Hallgrímsson because user.name is undefined for 10 seconds.
  3. {{ user.address.country }} will throw an error (Cannot read property 'country' of undefined) because user.address isn't available in the initial data object.

So one solution is to define address initially in the data object

user: {
  address: {}
}

My point is that your initial code should work (when rendering). The undefined errors come when

a) You're using that name property in your vue app, e.g user.name when there is no name property.

or

b) When you're rendering 2nd level property, e.g user.address.country because address hasn't been initially defined.

Upvotes: 1

Jerre
Jerre

Reputation: 179

You could add a 'loaded' boolean attribute to resolve this. For example:

<script>
    new Vue({
        el: '#container',
        data: {
            isLoaded: false,
            user: {}
        },
        mounted() {
            this.getUserData();
        },
        methods: {
            getUserData(){
                axios.post('/user/get').then((response) => {

                    this.isLoaded = true;

                    // Check the response was a success
                    if(response.data.status === 'success')
                    {
                        this.user = response.data.user;
                    }
                });
            },
        }
    })
</script>

Then wrap your html to check if the data has been loaded:

<div v-if="isLoaded">
   <!-- If user.name exists, display user.name -->
   <div v-if="user.name">
       @{{ user.name }}
   </div>
</div>

You could then also display a loader:

<div v-if="isLoaded">
   <!-- If user.name exists, display user.name -->
   <div v-if="user.name">
       @{{ user.name }}
   </div>
</div>

<div v-else>
    Loading...
</div>

Upvotes: 5

Related Questions