Reactivity headache


#1

So I have an object that I initialize on entering a view, but it can also be populated by the user “loading” an existing object in (see loadMagicObject()).

I have a load of form controls that all end up reference the reactive Session object, which is fine, other than that every time I change a single field, all the fields are going to get recalculated.

Surely there is a better way to achieve this?

Template.example.created = function () {
  resetMagicObject();
};

var resetMagicObject = function () {
  Session.set('magicObject', {
    name: '',
    owner: '',
    age: 0
  });
};

Template.example.events({
  'keyup .js-magic-name': function (event, template) {
    var magicObj = Session.get('magicObject');
    magicObj.name = $(event.target).val();
    Session.set('magicObject', magicObj);
  }
});

Template.example.helpers({
  selectedObject: function () {
    return Session.get('magicObject');
  },
  loadMagicObject: function () {
    var magicObj = MagicObjects.findOne();
    Session.set('magicObject', magicObj);
  }
});

And in my HTML:

<input type="text" class="js-magic-name" value="{{selectedObject.name}}" placeholder="Name..."/>

#2

Check out viewmodel.meteor.com, you might find it easier to deal with this situation then.


#3

If your question is “how do I store an object in Session and avoid triggering reactivity everywhere when only one field is modified”, then I can suggest 2 solutions:

  1. Split your object into its fields:

     var name = $(event.target).val(); 
     Session.set('magicObjectName', name);
    
  2. Use the old but still extremely useful package isolate-value:

     selectedObjectName: function () {
         return isolateValue(function() { return Session.get('magicObject').name; });
     },
    

#4

Ok first of, you do not have to use jQ to get the value. The event variable is a jquery event already, so you can do:

event.currentTarget.value
```
You can use `target` only, but [as per the docs][1] - "Most events bubble up the document tree from their originating element." So watch out for that ;)

Now to your original question: Since you are binding and event to the `keyup` you'll always update **ALL the  fields**- why? - simple - Sessions are reactive and you are updating the Session with each keyup event, which re-renders the template.

To sum it up - breakup the session to smaller pieces so you can update them separately, use the `name` attribute at inputs so you can be more DRY.

```
Template.example.events({
  'keyup .js-magic': function (event) {
      var name = event.target.name,
          value = event.target.value;
      Session.set('magic-' + name, value);
  }
});
```

**OR**

Do not bind the Session the the input value! That will update the input every time the Session changes!
I would suggest something like this:

```
 Template.example.helpers({
  selectedObject: function () {
     var object = MagicObjects.findOne();
     if(_.isObject(object)) { //lets use underscore here to see if the response is an object
         return object;
     } else {
         return '';
     } 
  }
```

Hope it helped ;)

  [1]: http://docs.meteor.com/#/full/eventmaps

#5

Maybe you should use reactive-dict instead of a session object.


#6

Why is that Solarc? Even the Atmosphere page you linked says that the ONLY difference is that it does not survive hot code pushes. Please correct me if I am wrong.


#7

Well I guess it is the same as splitting your object into its fields as Steve suggested. But it separates your object from other Session variables.


#8

Thanks for the suggestions @Steve & @davethe0nly, I’ll just post my thoughts on the methods you described:

@Steve:

  1. Splitting my objects into it’s fields was certainly an option, however as @solarc mentions later, I’d probably use a ReactiveDict to achieve this rather than the Session object (although I’d loose the data on a hot-code push).

  2. I’d not heard of the isolate-value package and it looks very interesting, although the fact it hasn’t been modified for almost 2 years makes me a little hesitant to make any code rely to heavily on it… Also was it abandoned because it was considered a bit of a “hack”?

@davethe0nly:

  1. Thanks for pointing out that the events are already jQuery events, saves me a lot of wrapping in a lot of places… Whoops! :blush:
  2. the target vs. currentTarget point was something I hadn’t realised at all, again, thanks for the tip.
  3. Essentially your first suggestion is the same as Steve’s first suggestion, but with a bit more reusability in the code. I like it, however it still feels very clunky… (Like a ReactiveObject class is just missing…)
  4. Your last point brings me onto a final thought…

If the object is either a document loaded from the database, or one awaiting to be saved into the database. Can I create a new document in the client collection only which I bind everything too (I think that would then be reactive on a per-field level), and then tell it to save it to the server when the users actually wants to save it?


#9

I don’t think isolate-value is a hack. I find it useful in many situations.

Can I create a new document in the client collection only […]?

I know no reliable way to do that. There is an undocumented API, but it has strange behavior.


#10

Hmm yeah, perhaps that isn’t a good idea at all then! :blush:

Perhaps I’ll take a closer look at isolate-value, but I think for the time being I’ll probably end up using a system like @davethe0nly suggested, as it’s easier for someone else to understand what it’s doing & how.