FlowRouter.go() in event handler creates subscription problems


#1

If I invoke FlowRouter.go() inside an event handler upon a successful form submission, I get the dreaded:

Exception in template helper: TypeError: Cannot read property 'first' of undefined

I do not get that error if I do not call FlowRouter.go().

Here is a two minute screencast illustrating the problem:

I would prefer not to do the “guard” hack; that does not seem like correct Meteor coding. If I do not have the console open, I don’t notice the error, but that also doesn’t seem like correct Meteor coding.

I do not understand why the helper functions for the Edit page are being called when FlowRouter is redirecting away from that page.

Thanks for any insight you can provide.


#2

just a few comments from what I can see,

The subscription is in an autorun(), but it isn’t guarded or being passed a param for a contact in particular to subscribe to, so you could probably remove the autorun.

the helpers for each key seem slightly redundant, which is why it feels even more redundant to guard it repeatedly.

possibly this could help with the template errors?

helpers({
  contact(){
    const id = FlowRouter.getParam('_id');
    return id && Contacts.findOne(id);
  }
})

in the html, use {{ contact.first }}

I think you are getting slight more delay by returning so many helpers that are doing the same thing. Blaze is more graceful about helpers failing or not being available, but your error is more related to an unguarded javascript variable, the param, that becomes unavailable when routing away.

also, depending on how you need the info updated, timing etc you can put the FlowRouter.go, before the update code, and let it process in the bg - if you don’t need to wait. If you do need to wait, for a potential error etc, you can move it to

Contacts.update(id, { $set: updateObj }, (err, res) => FlowRouter.go(...))


#3

I would also possibly refactor the error helper to take the key as a param, to reduce repeated code.

{{#if displayFieldError ‘first’}} or make it a global registerHelper so you can reuse easier.

helpers({
  displayFieldError(field) {
    if (Match.test(field, String) {
      ...validation code keyObj.name === field ...
      //param field instead of multiple duplicated helpers. 
    }
  }
})

#4

Thanks so much for the quick response.

I agree there is too much redundancy; I will remove it.

I was trying to avoid the use of this idiom:

return id && Contacts.findOne(id);

because I have never seen it used in “authoritative” documents (i.e. Meteor Guide), and it seems like it can mask other problems. However, it is a relatively simple way to avoid the spurious console errors, so I guess I will incorporate it.


#5

It’s not really hiding an error, it’s just not accessing a nested object property if the object isn’t available. The main time to not use it is if you need to act differently/show a different template/message if the object isn’t avail. If you don’t need to deal with that in the UX, then you can use the shorthand. If you use lodash in your app, my fav helper of all time is lodash.get(obj, ‘deep.nested.property’, null)

you can safely get an object property or return a default value if it’s unavail, without having to do multiple guards

Here is an article that is relevant about guards, it’s really just a more shorthand coding style that is becoming more popular in js

it’s the same as ternary

return id ? Contacts.findOne(id) : false

or longer if/else statement

const id = ...
if (Match.test(id, String)) {
  return Contacts.findOne(id); 
}

or usually, if I need a param over and over, you can assign it to the template instance in oncreated in the a reactive dict or autorun to subscribe to a document,

this.autorun(() => {
  // store contact's id on template for later reuse
  this.contactId = FlowRouter.getParam...
  if (id) {
    this.subContact = this.subscribe('contacts', this.contactId )
  }
}

and in the helpers

if (Template.instance().subContact.ready()) {
  ...
}

#6

That’s really, really helpful. Thanks so much for the detailed responses.