Still getting confused about reactivity... Help!

Okay, so I’ve created examples of both a non-working and a working version of my issue…
This is obviously a slightly contrived example, my real use-case is somewhat more complex, but, it highlights my point well enough.

Broken Version: http://meteorpad.com/pad/6e4qXnfdZvyfPKPhR/Reactivity%20with%20Autorun

Working Version: http://meteorpad.com/pad/CXXJxcWXQ9467xRY8/Reactivity%20with%20Helper

Essentially, what I expect to happen is the button press causes the score to go up by 5 points… The helper that adds the “***” is merely an example of a function that transforms the result in some way.

If the data being passed through from the parent template is reactive, then I would expect the autorun (in the broken example) to fire when it changes, but it doesn’t.

If the value isn’t reactive, I wouldn’t expect it to fire the helper in the second (working) example, but it does!

Can someone please explain what the hell is going on!?

EDIT: Someone has stated that it’s because Template.instance() is reactive and the template context isn’t… If that’s the case why doesn’t replacing the line: var score = self.data.score; with var score = Template.instance().data.score; cause the first example to start working?

As an example of where I am using this sort of strategy; I have a document reference to an associated Image stored as a nested-field inside my Product document. So when viewing this Product I’d like to have the subscription to the Image autorun, update a local value and rerender the image, if the value ever changes (much like the autorun in the broken example).

Lets see if I can explain this in a way that will make sense to you.

Whoever said data contexts are not reactive is completely right but a template instance is not reactive.

Basically the template instance registers a tracker dependency if the data context is set to reactive data source. It will then re-run the helpers on the template anytime the reactive source’s data changes and thus you get reactivity.

the template.autorun works in exactly the same way but needs a reactive data source inside the function to register the dependancy. In your example that’s not working, you use self.data.score which will not register a dependency to trigger the autorun once it gets invalidated. If you change this to Template.currentData().score you will get the desired result. Please note though that using auto run to set up and change a reactive var on a template isn’t really an optimal way to achieve reactivity.

1 Like

Ah okay, I have tried using Template.instance().data.score but that didn’t work. I assume that currentData() does something different!?

Yes, it returns a reactive data source. Much like doing a Players.find({ name: "Ada Lovelace" }).fetch()[0] would not be a reactive data source, but Players.findOne({ name: "Ada Lovelace" }) would be.

1 Like

Okay, I had no idea about currentData() thanks for that pointer! :wink:

No problem… Just in case you need some further reading :slight_smile: http://docs.meteor.com/#/full/template_currentdata

I’m running into a tricky side-effect of Template.currentData() being overly reactive. Say I have an object loaded into a template that looks like this:

{
   totalPoints: 300,
   players: 5,
   minutesLeft: 3,
   //  Lots more fields
}

I want to do some UI updates anytime the minutesLeft updates:

instance.autorun(function() {
   currentMinutesLeft = Template.currentData().minutesLeft;
   //  A bunch of other code
});

I’m running into an issue where this autorun will fire if any of the above object’s fields get updated. This isn’t ideal as the autorun is way over-firing as my actual code is much more complex.

Is there a way to get currentData() to only establish a reactive dependency on a given field? Or is there some other way to do this that I’m missing using a find on the object in minimongo?

Check out https://gist.github.com/veered/30ae6f79ec48506af80f for some extensions I made to Tracker.

What you want is:
currentMinutesLeft = Tracker.guard(() => Template.currentData().minutesLeft)

Only copy the Tracker.guard implementation into your code though. The stuff below Tracker.memo is experimental and might or might not work.

1 Like

Your Tracker.guard() did the trick. Thank you.

It’s kind of a bummer though that a solution like this is required. Is there no other standard way to work around this?

1 Like

Something else Tracker.guard() just helped me with:

I use aldeed:meteor-template-extension to easily reference parent template ReactiveVars from child templates. For some reason when using this package’s parent() function like this in an autorun:

instance.autorun(function() {
   if(instance.parent(1, false).someReactiveVar.get())
      //...
});

It would always fire the autorun in the child template. Even if it was completely unaffected in the parent template. Tracker.guard() now stops this from firing unless its value actually gets changed.

instance.autorun(function() {
   let parentValue = Tracker.guard(() => instance.parent(1, false).someReactiveVar.get());
   if(parentValue)
      //...
});

This only happens when using this package’s parent() function in an autorun. Otherwise, ReactiveVar is working as expected inside an autorun.

So, my autorun woes were two-fold. Overly reactive Template.currentData() firing on any field that gets updated and calling the parent() function of aldeed:meteor-template-extension on a parent’s ReactiveVar from a child template.

1 Like

Glad it helped! I’ve got a bunch of Tracker extensions that I’ve been thinking about bundling into a package, but Tracker.guard is by far the most useful. Kind of crazy something like it isn’t built into Tracker by default.

We’re actually finding an issue with Tracker.guard (yes, I know it’s four years later :laughing: ). Just thought I’d throw it in here to see if it clicks with anyone.

Say we have a highly reusable Blaze table template for viewing table data that takes an object of columns that is defined by the table’s parent like so:

{{> dataTable extendContext columns=getColumns}}

We have a simple helper:

getColumns: function() {
    return Template.instance().columns;
}

And then we have an autorun that updates the columns based on a data field tableFormat from the main data context document from collection Items:

instance.autorun(function() {
   const tableFormat = Tracker.guard(() => Template.currentData().tableFormat);
   switch(tableFormat) {
      // Rebuild the columns object based on the current table format
      instance.columns = ...;
   }
});

Using Tracker.guard()

I’m noticing that if we use Tracker.guard() the child table will re-render correctly only once based on the updated columns object definition. Then after the first correct re-rendering, the table will always be one update behind.

For example:

  • Upon load, tableFormat specifies a table with 5 columns and a 5-column table is created.
  • Then tableFormat is updated to specify an 8 column table, the table will update correctly to an 8 column table.
  • Then if tableFormat is updated to specify a 4 column table, nothing happens.
  • Then if tableFormat is updated to specify a 7 column table, the table will update to be a 4 column table.
  • And so on, with the updated table always being one update behind.

The weird thing is if I log tableFormat within the autorun, it’s always updated correctly. It’s the dataTable template’s rendering that is always behind one update.

Using Template.currentData() Only

If I replace the above Tracker.guard() line with just:

const tableFormat = Template.currentData().tableFormat;

The table updates perfectly upon every update to tableFormat. However, the problem with this (as I mention above in several posts back) is that every field (e.g. title, updatedAt, etc.) on the main data context Item (which has a lot of fields) will trigger this autorun to re-run. And all those other fields have nothing to do with this block of code. So we don’t want that. Otherwise we’d have autoruns firing everywhere.

Using Minimongo (Current Solution)

Upon a lot of experimentation, it seems like just doing a mini-mongo query within the autorun is the solution that works the best. As it always updates the table correctly and it doesn’t rerun the autorun when other fields change. So if I replace the above Template.currentData() line with:

const currentItem = Items.findOne({_id: itemId}, {fields: {tableFormat: 1}});

It all works correctly.

My question is… why? Why does Tracker.guard() go into a state where it’s always one update behind?