Allow + autorun() + update = infinite loop


#1

In autorun() I update record in MiniMongoDB
Server then uses allow update callback which always returns false to figure out that change needs to be reverted.
This then triggers reactive code inside autorun() which repeats the same update in MiniMongoDB.
And this goes on in infinity with error “update failed: Access denied” inside JavaScript Console.

So my question is how to handle these kind of situations.
Should I avoid having code that updates data base inside reactive block?
Should I avoid using Allow and Deny and switch to Server Methods without stubs?

App.html

<body>
  {{#each people}}
    <li> Hello {{name}} is {{age}} years old </li>
  {{/each}}
</body>

App.js

//CREATE LOCAL AND REMOTE COLLECTIONS.
people = new Mongo.Collection('people');  
people.allow({
  update: function(userId, doc) {
    return false;
  }
});

//SERVER.
if (Meteor.isServer) {
  
  //CLEAR COLLECTION.
  people.remove({});
    
  //INSERT DOCUMENTS.
  people.insert({ name : "John", age : 20 });
  people.insert({ name : "Bill", age : 30 });
  people.insert({ name : "Lucy", age : 40 }); 
  
}

//CLIENT.
if (Meteor.isClient) {
  
  console.log("Template.subscriptionsReady="+Template.subscriptionsReady);
  
  //UPDATE PERSON.
  Tracker.autorun(function () {
    person = people.findOne( {name : "John"} ); 
    if(person) { 
      people.update( person._id, {$set : {age: 60}} ) 
    };    
  });
    
  //BODY HELPERS.
  Template.body.helpers({   
    people: function() {
      return people.find(); 
    }  
  });  
    
}

#2

The problem is just this block I think:

Tracker.autorun(function () {
    person = people.findOne( {name : "John"} ); 
    if(person) { 
      people.update( person._id, {$set : {age: 60}} ) 
    };    
  });

This block reruns when the record “John” changes. You also change the record “John” in this block. -> Infinite loop. Depending on what you want to achieve:


#3

Have you tried wrapping the update in a Tracker.nonreactive?

Tracker.autorun(function () {
    person = people.findOne( {name : "John"} ); 
    if(person) { 
      Tracker.nonreactive(function(){
        people.update( person._id, {$set : {age: 60}} ) 
      });
    };    
  });

You could also search for anyone named “John” whose age isn’t already 60… I would almost be inclined to do both, because I’m not sure if Meteor is smart enough to kill the computation once the search returns no result.


#4

Do you got to fix this? I am having the same infinite loop problem with this method inside the autorun:

Template.contenidoOrden.rendered = function () {
  this.autorun(function (c) {
    if (!this.subscription.ready()) {
      IonLoading.show();
    } else {
      
if(FormaDePago.find({idUsuario: Meteor.userId()}).count()){
      var formaDePago = FormaDePago.findOne({favorita: "checked"});
      console.log(formaDePago);
      var idFormaDePago = formaDePago[0]._id;
      Meteor.call("actualizarIdFormaDePago", idFormaDePago);
    }else{
      Meteor.call("borrarIdFormaDePago");
    }
      IonLoading.hide();
    }
  }.bind(this));
};

Any idea how I cna make this infinite loop stop?


#5

My problem was that I was updating DB from client, then server would revert the change since it was invalid changing DB and triggering reactive source again. In my case switching to Server Methods I think worked. But you are already using Server Methods. Unless your server methods change DB every time they are called. If reactive code always changes reactive data it uses then loop happens. I didn’t try what was suggested by others since it looked to complicated. Sorry if this doesn’t help.


#6

I think your problem is with your subscription ready test:

Template.contenidoOrden.rendered = function () {
  this.autorun(function (c) {
    if (!this.subscription.ready()) {

The line if (!this.subscription.ready()) { is incorrect for two reasons:

  1. The this object should be the template instance’s context, not the autorun’s context.
  2. There is no subscription property on the template instance’s context.

Basically, the test always fails and so it loops. Your code should read:

Template.contenidoOrden.rendered = function () {
  var self = this;
  this.autorun(function (c) {
    if (!self.subscriptionsReady()) {

As an additional comment, the rendered property of a Template is deprecated. You should consider using onRendered:

Template.contenidoOrden.onRendered(function () {
  ...
});

#7

My two cents here : most of the time, when i’m trying to do something and my first idea is to use an autorun, 99% of the time i’m wrong and i end up finding another better solution. I won’t say this is an antipattern because sometimes there is clearly no other way to do it, but this should be avoided when possible.

Regarding the specific examples shown here, in the first case :

Tracker.autorun(function () {
    person = people.findOne( {name : "John"} ); 
    if(person) { 
      people.update( person._id, {$set : {age: 60}} ) 
    };    
  });

I don’t see where this could be useful. If you really need to update a document as soon as it is inserted in the db, then modify it before insertion. If you can’t rely on the client to do the modification, then make them use a server side method that will do the modification itself.

About the second example :

Template.contenidoOrden.rendered = function () {
  this.autorun(function (c) {
    if (!this.subscription.ready()) {
      IonLoading.show();
    } else {

You are waiting for the template to be rendered, then you start an autorun to watch for the subscription, which will then manage the display of another template as long as a the subscription is not ready… this seems very complicated, very procedural and not reactive/meteorish.
I think your spinner should have it’s own template and then you are just including the spinner template based on an helper watching your subscription in a parent template.

I may be 100% wrong about this, but i think the usual correct way to create a computation is by using a template. Autorun is most of the time hacky and procedural, if this then this then that…


#8

Agreed!

And, yes, autorun is easily abused and can lead to redundant/unnecessary recomputations.