Collection2 and meteor-collections-hooks conflict causes infinite loop

We use both the collection2 and the collection-hooks packages inside of our meteor application. That currently causes an infinite loop after an document is inserted or updated. We use Collection2’s autovalue to set/update things like createdAt and updatedAt like shown below.

An excerpt from the document schema:

createdAt: {
    type: String,
    label: "The date this document was created.",
    autoValue() {
      if (this.isInsert) return (new Date()).toISOString();
    },
  },`

After an document is updated or inserted we need to geocode an address to coordinates and store them. For that we use collection-hooks.

An excerpt from the document hooks:

Documents.after.insert((async (userId, doc) => {
  const documentId = doc._id;
  const address = doc.address;

  const result = await geocoder.geocode(`${address.street} ${address.zip} ${address.city} ${address.country}`)
    .then((res) => {
      // Log geocoder result when in development env
      if (Meteor.isDevelopment) console.log(res); // eslint-disable-line
      return res;
    })
    .catch((exception) => {
      throw new Meteor.Error("500", exception);
    });

  Documents.update(documentId, {
    $set: {
      "coordinates.lat": result[0].latitude,
      "coordinates.lon": result[0].longitude,
    },
  }, {
    validate: false,
  });
}));

Unfortunately does this approach cause an infinite loop. After you update or insert a document the hook gets triggered which then I guess triggers again the autovalue and so on…

Has anyone any suggestion how I can fix or improve this?

You can check the existence of “sibling” fields inside the same document when inside the autoValue() handler. If lat and lon are already set, you won’t set the value. Not sure if that works, though.

Another idea would be this (from the Collection2 docs):

To skip adding automatic values, set the getAutoValues option to false when you call insert or update. This works only in server code.

You could try to set this in the update() in your .after.insert() handler.

1 Like

Hey, so you mention that

So do you also have something like the following ?

Documents.after.update((async (userId, doc) => {
    *** Some logic here **
  Documents.update(documentId, { //This probably trigger after.update
    $set: {
      "coordinates.lat": result[0].latitude,
      "coordinates.lon": result[0].longitude,
    },
  }, {
    validate: false,
  });
}));

If so, that might cause the infinite loop, because whenever you update a document, after.update hooks is triggered, which updates the document inside of your code block, and this again triggers after.update hook.

Maybe you may try storing geocodes in a separate collection. So that you wouldn’t have nested hooks for the same collection which can cause infinite loops.

For instance, if you update a document you can do the following

Documents.after.update((async (userId, doc) => {
  const documentId = doc._id;
  const address = doc.address;

  const result = await geocoder.geocode(`${address.street} ${address.zip} ${address.city} ${address.country}`)
    .then((res) => {
      // Log geocoder result when in development env
      if (Meteor.isDevelopment) console.log(res); // eslint-disable-line
      return res;
    })
    .catch((exception) => {
      throw new Meteor.Error("500", exception);
    });
   Geocode.update(documentId, {
    $set: {
      "coordinates.lat": result[0].latitude,
      "coordinates.lon": result[0].longitude,
    },
  }, {
    validate: false,
  });
}));

Or instead of this, you can update your methods in a way that, instead of relying on collection-hooks, you can insert or update a document once you have the result from the following code snippet

const result = await geocoder.geocode(`${address.street} ${address.zip} ${address.city} ${address.country}`)
    .then((res) => {
      // Log geocoder result when in development env
      if (Meteor.isDevelopment) console.log(res); // eslint-disable-line
      return res;
    })
    .catch((exception) => {
      throw new Meteor.Error("500", exception);
    });
1 Like