Blaze - callback is called when passed to another template

Following the advice in the Blaze guide, I’m passing callbacks down from my smart templates to my reusable templates.

But every time I pass a callback down, it gets called. If I pass it down several times, it basically crashes the browser.

After reading this discussion - Blaze Bugs when passing callbacks - I refactored, always passing down an object rather than passing the callback itself:

<Template name="FirstChild">
  {{> SecondChild (childArgs callback1 callback2)}}
</Template>

Template.FirstChild.helpers({
  childArgs(callback1, callback2) {
    return { callback1, callback2 };
  }
})

To solve this, I have to wrap the callbacks in a function that returns them every time I pass them down to other templates

So the helper is always:

Template.nthChild.helpers({
  childArgs(callback1, callback2) {
    return {
     callback1() { return callback1 },
     callback2() { return callback2 } 
    };
  }
})

I have to keep doing this until the template where it actually gets called by an event (at which point it’s fine to just pass the unwrapped function).

I don’t know how else to put this, but what the £$!&^&"@#!&!! is going on???

Is this normal?? I appreciate that blaze just calls helpers over and over, but this feels like there’s either a bug, or I’m doing Blaze wrong

Any thoughts?

What kind of data or what is the callback1/callback2 code returning, just as an example? Would help me provide advice, since there are a few options to handle this. Thanks

You could do something more generic:

  byRef(prop) {
    return () => callbacks[prop];
  }
<template name="Parent">
  {{> Child cb1=(byRef 'callback1') cb2=(byRef 'callback2')  }}
</template>
1 Like

The callbacks are just used to call methods in the parent templates (smart templates) - basically they’re just for onSubmitForm type stuff.

So the "submit form" event calls this.onSubmitForm(post) and that calls the "insertPost(post)" method.

(Just following what it says here - http://blazejs.org/guide/reusable-components.html#Pass-callbacks - unless I’ve missed the point??)

So, I thought the reason this was happening was because I was passing in the callbacks individually as you have - which is why I refactored to eliminate this. But wouldn’t your method suffer from the same issues if the callbacks were passed down to a second/third/nth child?

(Thanks BTW, obviously I don’t mind refactoring again if this is the solution! More than anything, I’m really confused as to why this is happening - surely can’t be “normal”?!)

You know how to pass a callback from template 1 to template 2, that means you can pass it from template 2 to template 3.

btw, it is normal behavior. When you do (childArgs callback1) Blaze sees callback1, notices it’s a function and resolves it to get the value coming out of that function. It makes more sense if you replace the names: (printText getMessage). Blaze: "Let me get the message by running getMessage() and pass the result to the printText function.

The below code will not execute automatically. It will allow you to pass down parameters or custom functions with arguments, and then only execute once they are invoked on the child component. You can also define how you have access to data context etc. Let me know of any thoughts or questions. I have a few different strategies for passing complex data or functions/instances around.

<template name="uniqueDataForm">
  <div class="row">
    <div class="column">
      {{> genericActionButton lib=(actions message) }}
    </div>
  </div>
</template>

<template name="genericActionButton">
  <button class="ui button">
    Perform Smart Action
  </button>
</template>


Template.uniqueDataForm.helpers({
  message () {
    return 'sample form/input value'
  },
  actions (message) {
    return {
      action () {
        console.log('actions.action function() message:', message)
      }
    }
  }
})


Template.genericActionButton.events({
  'click button': function(e, t) {
    console.log('click genericActionButton template:', t)
    console.log('click genericActionButton this:', this)
    if (t.data.lib && t.data.lib.action) {
      return t.data.lib.action()
    }
  } 
})

Here is that same example taken arbitrarily further, to show that you can chain advanced callbacks from different helpers, and they don’t get invoked except when needed.

I just used separate helpers to simulate them being defined from different templates, I wouldn’t code like this normally.

This is passing in dynamic items for which collection is being used, what insert object, and what callback should be used after the mongoDB insert.

// cleaned up, don't need .bind(null, ...) etc if using es6 arrow functions, unless you need more control over arguments or data context


<template name="uniqueDataForm">
  <div class="row">
    <div class="column">
      {{> genericActionButton lib=(actions message) db=(dbActions formObj 'Clients' actions2) }}
    </div>
  </div>
</template>

<template name="genericActionButton">
  <button class="ui button">
    Perform Smart Action
  </button>
</template>

Template.uniqueDataForm.helpers({
  actions2 () {
    return {
      finalAction (collection, err, res) {
        collection && console.log('finalAction collection:', collection)
        err && console.log('finalAction err:', err)
        res && console.log('finalAction res:', res) // will log doc ID from insert
      }
    }
  },
  dbActions (obj, collection, actions) {
    return {
      insert () {
        if (Match.test(global[collection], Mongo.Collection)) {
          return global[collection].insert(obj, actions.finalAction.bind(null, collection)) 
          // this will add collection to the mongodb callback of err/res, if needed
        }
      }
    }
  },
  formObj () {
    return { name: 'ZZZ11 Test Company' }
  },
  message () {
    return 'sample form/input value'
  },
  actions (message) {
    return {
      action() {
        console.log('actions.action function() message:', message)
      }
    }
  }
})


Template.genericActionButton.events({
  'click button': function(e, t) {
    console.log('click genericActionButton template:', t)
    console.log('click genericActionButton this:', this)
    if (t.data.lib && t.data.lib.action) {
      t.data.lib.action()
    }
    if (t.data.db && t.data.db.insert) {
      t.data.db.insert()
    }
    return
  } 
})


Many thanks @sbr464 & @manuel - this is all making a lot more sense now (though I’ll have to digest and think on the solutions a bit!) :relaxed:

Hopefully this is something that can be simplified with Blaze 2 if that ever happens…

Just use ViewModel. I’d recommend going with the React version but the Blaze version will still simplify your code (A LOT). All these runarounds just aren’t needed.

I’ve been meaning to take a look at that - but with only 29 days to go until launch, it will have to be saved for the next refactoring!