Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VueJS async component data and promises

Trying out VueJS 2.0 RC, and using the fetch API to load some data for some of the components. Here's a mock example:

const Component = {
  template: '#comp',
  name: "some-component",
  data: function () {
    return {
      basic: data.subset,
      records: function () {
        return fetch('/datasource/api', {
          method: 'get'
        }).then(function (response) {
          return response.json();
        }).then(function (response) {
          if (response.status == "success") {
            return response.payload;
          } else {
            console.error(response.message);
          }
        }).catch(function (err) {
          console.error(err);
        });
      }
    }
  }
};

The data object is a global application state, defined before the app's initialization, and only a subset of it is needed in the component, hence that part. The main set of the component's data comes from another endpoint which I'm trying to call. However, the data property expects an object, and it's getting back a Promise it can't really use in the context of a loop in the template for rendering etc (e.g. v-for="record in records")

Am I going about this the wrong way? What's the approach to get the data records property to update once it's fully fetched? Is there a way to force the code to stop until the promise resolves (effectively making it sync)? The component is useless without the data anyway, so there's a waiting period before it can be used regardless.

What is the right way to asynchronously populate a component's data field without using plugins like vue-async or vue-resource?

(I know I could use the XHR/jQuery methods of non-promise ajax calls to do this, but I want to learn how to do it using fetch)


Update: I tried defining a created hook, and a method to load the data, like this but no dice - in my own experiments, the records property fails to update once it's loaded. It doesn't change, even though the data is successfully fetched (successfully logs into console).

Could have something to do with me using vue-router and the component I'm dealing with triggering when a link is clicked, as opposed to being a regular Vue component. Investigating further...


Update #2: If I continue investigating the jsfiddle approach above and log this.records and response.payload to the console, it shows the records object being populated. However, this doesn't seem to trigger a change in the template or the component itself - inspecting with Vue dev tools, the records property remains an empty array. Indeed, if I add a method logstuff and a button into the template which calls it, and then have this method log this.records into the console, it logs an observer with an empty array:

enter image description here

Now I'm wondering why the data property of a component would be not updated even after it's explicitly set to another value. Tried setting up a watcher for $route that triggers the reload method, but no dice - every subsequent load of that route does indeed re-issue the fetch, and the console logs this.records as populated (from within fetch), but the "real" this.records remains an empty array.

like image 599
Swader Avatar asked Aug 28 '16 22:08

Swader


People also ask

How do I share data between Vue components?

VueJS props are the simplest way to share data between components. Props are custom attributes that we can give to a component. Then, in our template, we can give those attributes values and — BAM — we're passing data from a parent to a child component!

What does data () do in Vue?

data # A function that returns the initial reactive state for the component instance. The function is expected to return a plain JavaScript object, which will be made reactive by Vue.

Is Vue synchronous or asynchronous?

Conclusion. Vue updates the DOM asynchronously; tests runner executes code synchronously instead.

What are the 3 parts of a component in Vue?

Components in Vue are composed of three parts; a template (which is like HTML), styles and JavaScript. These can be split into multiple files or the same .


1 Answers

The solution was the jsfiddle approach, with a twist. This part:

reload: function () {
      fetch('/data/api', {
        method: 'get'
      }).then(function (response) {
        return response.json();
      }).then(function (response) {
        if (response.status == "success") {
          this.records = response.payload;
        } else {
          console.error(response.message);
        }
      }).catch(function (err) {
        console.error(err);
      });
    }

needed a .bind(this) in the part which sets the this.records value after the fetch, like so:

reload: function () {
      fetch('/data/api', {
        method: 'get'
      }).then(function (response) {
        return response.json();
      }).then(function (response) {
        if (response.status == "success") {
          this.records = response.payload;
        } else {
          console.error(response.message);
        }
      }.bind(this)).catch(function (err) {
        console.error(err);
      });
    }

This post was what made it click for me.

Lesson learned: in modern JS, always keep scope in mind.

like image 125
Swader Avatar answered Sep 28 '22 04:09

Swader