user2191247
user2191247

Reputation:

Updating array of objects

I have an array items of objects:

  data() {
    return {
      items: [
          {
              id: 101,
              firstName: 'Mohammed',
              lastName: 'Chang',
              dob: '1995-03-21'
          },
          {
              id: 102,
              firstName: 'John',
              lastName: 'Smith',
              dob: '1989-10-11'
          },
          {
              id: 103,
              firstName: 'Lulu',
              lastName: 'Lemon',
              dob: '1945-01-29'
          },
      ]
    }
  },

If I use a regular watch, and try to compare the items, because the items are objects, they will reference the same thing, so they will always return as unchanged:

 watch: {
    items: {
        handler: function(revised, original) {
            for (let i = 0; i < revised.length; i++) {
                // check each item in revised to see if it's changed from original
                if (this.isChanged(original, revised[i])) {
                    axios.put(URL + '/' + revised[i].id, revised[i]);
                }
            }
        },
        deep: true
    }
  },
  methods: {
      isChanged(original, item) {
        // this function won't work because item is an object
        // which will be referenced in original, so it will always
        // be a match, i.e. not changed
        for (let i = 0; i < revised.length; i++) {
          // found a matching ID in the original
          if (original[i].id === revised.id) {
              // compare the item against the original and return
              // true for changed and false for not changed
              return JSON.stringify(original[i]) !== JSON.stringify(item);
          }
          // no match? assume true
          return true;
        }
      }
    }
  }

If I use a watch like this, by referencing each item directly, then this will work:

 watch: {
    'items.0': {
        handler: function(revised) {
            axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    },
    'items.1': {
        handler: function(revised) {
            axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    },
    'items.2': {
        handler: function(revised) {
             axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    }
  },

However this approach is not very practical. What if I dynamically add an item to the array? It won't have a watcher.

Is there a way to do a watch like above items.# but using wildcards or regular expressions?

Because there's no way to compare the original object, do most developers just update each element in the entire array with its own PUT call? This seems wasteful, especially if dealing with many very large objects.

 watch: {
    items: {
        handler: function(revised) {
            // no way to know which item is changed, so just
            // update each item - wasteful, I know
            for (let i = 0; i < revised.length; i++) {
                axios.put(URL + '/' + revised[i].id, revised[i]);
            }
        },
        deep: true
    }
  },

Upvotes: 0

Views: 347

Answers (1)

Vladislav Ladicky
Vladislav Ladicky

Reputation: 2489

If I use a regular watch, and try to compare the items, because the items are objects, they will reference the same thing, so they will always return as unchanged:

Yes, in cases like this you have not available the previous value in the handler, only new one.

If I use a watch like this, by referencing each item directly, then this will work:

Yes, it will. Because you are not watching nested objects anymore.

However this approach is not very practical. What if I dynamically add an item to the array? It won't have a watcher.

Right, it will not have a watcher.

Is there a way to do a watch like above items.# but using wildcards or regular expressions?

No, there is nothing like that.

Because there's no way to compare the original object, do most developers just update each element in the entire array with its own PUT call? This seems wasteful, especially if dealing with many very large objects.

I'm lost... Why should anyone update every element in entire array? What do you mean by "own PUT call"? Can you edit your question and give me some code example with that PUT call? Can you show me what you're trying to do? To achieve up?

EDIT

Now I understand... First: every application should have only one point of truth. And in your case it is the external database, not the SPA itself. SPA just visualize that data. So, core of your problem can be solved (not only) that way:

Grab the data from source of truth, from backend. For example during the app creation:

new Vue({
  el: '#app',

  data: {
    items: []
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })
  }
}

Then do not change them directly. For example, like this:

new Vue({ el: '#app',

  data: {
    items: []
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })

    // Bad
    setTimeout(() => {
      this.items[1].firstName = 'Jan'
    }, 2000)
  }
}

But this way:

new Vue({ el: '#app',

  data: {
    items: []
  },

  methods: {
    updateItems (idx, obj) {
      this.item[idx] = { ...this.items[idx], ...obj }
    }
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })

    // Good, through some kind of setter
    setTimeout(() => {
      updateItems(1, { firstName': 'Jan' })
    }, 2000)
  }
}

What is it good for? Now, in that setter you can start to test new value if it is different like existing one, before you make any change. And not only. If you detect some changes, still do not mutate items array. Post this change to the backend with axios first, and change items only when backend respond the change was successful.

Upvotes: 1

Related Questions