Vue.js: How can I update a single item in a "list" - computed property?

3.7k Views Asked by At

I'm trying to determine the best pattern to solve the following:

  • I want to show a list of people in a specific department
  • I made the department index a regular, reactive Vue property
  • I made the list of people in the department a computed property

Now:

  • My backend (a Mac app) can dispatch a "Person at index changed" event and I must reload the name of a single person.
  • But: the person property is an item in a computed property (i.e. "people", see code below).

How can I update the name of a person in the list list of people, which in turn is a computed property (although it is "computed" from the departmentIndex + backend call)?

I assume that I have a mistake in my original setup. Maybe the people list should not be a computed property in the first place?

Here is the code:

function pretendUpdateEventFromBackend() {
   var event = new CustomEvent('PersonUpdated', {detail:{index:1, name:'Jimmy'}});

  document.dispatchEvent(event);
}

var backend = {
  // The actual backend is a Mac app with an embedded WebView...
  listPeopleInDepartment(departmentIndex) {
    return [
      {name:'John'},
      {name:'James'},
      {name:'Jane'}
    ];
  }
}

var app = new Vue({
  el: '#app',

  data: {
      message:'',
      departmentIndex:0,
  },

   computed: {

    people() {
      return backend.listPeopleInDepartment(this.departmentIndex);
    }
  },

  created() {
    const me = this;
    document.addEventListener('PersonUpdated', function(e){

      me.message += 'Updated ';

      var personIndex = e.detail.index;
      var newName = e.detail.name;

      // How can I update person in the list of computed people here?
      // Or how can I force a reload of the people list?

      me.message += 'Person at: ' + personIndex + ' new name: ' + newName + "\n";  
    });
  },
});

Html:

      <button onclick="pretendUpdateEventFromBackend()">Trigger Update Event from Backend</button>

      <div id="app">

        <div class="person" v-for="person in people">
          Name: {{ person.name }}
        </div>

        <pre>{{message}}</pre>

      </div>

EDIT:

Try it on jsbin: http://jsbin.com/guwezoyena/1/edit?html,js,output

1

There are 1 best solutions below

1
On

I just thought I should give you some further insight on your setup:

If listPeopleInDepartment is an ajax call in your actual code, this might be a bad pattern. See, Vue will identify every property (including other computed properties) that is used within a computed property and make it sure to re-compute it whenever any of them changes. In your case, this would mean whenever departmentIndex changes it'll recompute people.

Here's why it might be problematic, since you must return the result of the ajax request, it can't be asynchronous so it'll be a blocking call that would render the page unresponsive while it runs. Also, let's say you were viewing department 1, then you change to 2, if you go back to 1 it'll have to reload the people in department 1 because it's not stored anywhere.

You should probably implement some caching strategy and have ajax loads only when that data isn't already available, also in a non blocking fashion. You could achieve this with an array that stores the arrays of peopleInDepartment indexed by department id, and a watcher for departmentIndex that would do something like this:

function (newValue){
    if (!this.peopleCache[newValue]){
        var that = this;
        //Load it via ajax
        loadPeopleInDepartment(newValue, function(result){
            that.peopleCache[newValue] = result;
        });
    }
}