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?

1 Like

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>
2 Likes

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()
    }
  } 
})
1 Like

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!

Sorry to reopen this, but I was struggling with this recently.

It does still seem very wacky that the Blaze docs tell you to put reusable code/functions onto the instance.

And that same guide then says the way to pass callbacks down to children templates is by creating a helper that puts those callbacks on the data context! :crazy_face:

If you put reusable functions on the instance context why would you want them passed into the data context and have to remember this wackiness?

It would be nice if Blaze had a simple way to just pass functions (without calling them), reactive vars, etc. to the child instance context and not the child instance data context. This would be so much more in align with React (that makes this extremely simple).

One of the ways to solve this is to utilise a package that extend templates with .parentTemplate(). Your child templates can then have their own helpers that call from the parent template’s instance.

A quick search on atmosphereJS should throw up 1 or 2 package options.

We used this paradigm for years and it’s very bad. This makes you unable to truly componetize your templates. When some highly reusable child template needs to call .parentTemplate(level), then as soon as that child template is moved or placed in a different hierarchy, it all breaks.

A much better, reusable paradigm is to pass into the template whatever functions, callbacks, static labels, etc. it needs.

A child template shouldn’t worry about its parent. The parent simply should pass down to the child.

2 Likes