Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to induce reactivity when updating multiple props in an object using VueJS?

Tags:

I was witnessing some odd behaviour while building my app where a part of the dom wasn't reacting properly to input. The mutations were being registered, the state was changing, but the prop in the DOM wasn't. I noticed that when I went back, edited one new blank line in the html, came back and it was now displaying the new props. But I would have to edit, save, the document then return to also see any new changes to the state.

So the state was being updated, but Vue wasn't reacting to the change. Here's why I think why: https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects

Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive

Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object

The Object in my state is an instance of js-libp2p. Periodically whenever the libp2p instance does something I need to update the object in my state. I was doing this by executing a mutation

syncNode(state, libp2p) {
    state.p2pNode = libp2p
}

Where libp2p is the current instance of the object I'm trying to get the DOM to react to by changing state.p2pNode. I can't use $set, that is for single value edits, and I think .assign or .extend will not work either as I am trying to replace the entire object tree.

Why is there this limitation and is there a solution for this particular problem?

like image 301
AustinFoss Avatar asked Apr 02 '20 02:04

AustinFoss


People also ask

How does reactivity work in Vuejs?

Vue's reactivity system works by deeply converting plain JavaScript objects into reactive proxies. The deep conversion can be unnecessary or sometimes unwanted when integrating with external state management systems (e.g. if an external solution also uses Proxies).

Are Props reactive in Vue?

Props and data are both reactiveWith Vue you don't need to think all that much about when the component will update itself and render new changes to the screen. This is because Vue is reactive. Instead of calling setState every time you want to change something, you just change the thing!

What does $Set do in Vue?

js $set() method to set data object properties. Binding a class on an item and controlling it by the truthy value of a data property is a powerful feature of Vue.


2 Answers

The only thing needed to reassign a Vuex state item that way is to have declared it beforehand.

It's irrelevant whether that item is an object or any other variable type, even if overwriting the entire value. This is not the same as the reactivity caveat situations where set is required because Vue can't detect an object property mutation, despite the fact that state is an object. This is unnecessary:

Vue.set(state, 'p2pNode', libp2p);

There must be some other problem if there is a component correctly using p2pNode that is not reacting to the reassignment. Confirm that you declared/initialized it in Vuex initial state:

state: {
  p2pNode: null  // or whatever initialization value makes the most sense
}

Here is a demo for proof. It's likely that the problem is that you haven't used the Vuex value in some reactive way.

like image 54
Dan Avatar answered Oct 13 '22 22:10

Dan


I believe your issue is more complex than the basic rules about assignment of new properties. But the first half of this answer addresses the basics rules.

And to answer why Vue has some restrictions about how to correctly assign new properties to a reactive object, it likely has to do with performance and limitations of the language. Theoretically, Vue could constantly traverse its reactive objects searching for new properties, but performance would be probably be terrible.

For what it's worth, Vue 3's new compiler will supposedly able to handle this more easily. Until then, the docs you linked to supply the correct solution (see example below) for most cases.

var app = new Vue({
  el: "#app",
  data() {
    return {
      foo: {
        person: {
          firstName: "Evan"
        }
      }
    };
  },
  methods: {
    syncData() {
      // Does not work
      // this.foo.occupation = 'coder';

      // Does work (foo is already reactive)
      this.foo = {
        person: {
          firstName: "Evan"
        },
        occupation: 'Coder'
      };

      // Also works (better when you need to supply a 
      // bunch of new props but keep the old props too)
      // this.foo = Object.assign({}, this.foo, {
      //  occupation: 'Coder',
      // });      
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  Hello {{foo.person.firstName}} {{foo.occupation}}!
  <button @click="syncData">Load new data</button>
</div>

Update: Dan's answer was good - probably better than mine for most cases, since it accounts for Vuex. Given that your code is still not working when you use his solution, I suspect that p2pNode is sometimes mutating itself (Vuex expects all mutations in that object to go through an official commit). Given that it appears to have lifecycle hooks (e.g. libp2p.on('peer:connect'), I would not be surprised if this was the case. You may end up tearing your hair out trying to get perfect reactivity on a node that's quietly mutating itself in the background.

If this is the case, and libp2p provides no libp2p.on('update') hook through which you could inform Vuex of changes, then you might want to implement a sort of basic game state loop and simply tell Vue to recalculate everything every so often after a brief sleep. See https://stackoverflow.com/a/40586872/752916 and https://stackoverflow.com/a/39914235/752916. This is a bit of hack (an informed one, at least), but it might make your life a lot easier in the short run until you sort out this thorny bug, and there should be no flicker.

like image 28
AlexMA Avatar answered Oct 13 '22 22:10

AlexMA