Trying to grasp Meteor's reactivity has been quite the journey. The concept of it triggering a page re-render when a reactive data source in a template changes makes sense to me. I've also got a good handle on what qualifies as a reactive source - things like Session and MongoDB cursors.
However, I've hit a roadblock when it comes to understanding these mysterious "behind the scenes" calls to my template helpers. There seems to be more than just reactivity at play here.
In particular, within the code snippet below, there's a helper called friendRequests
that sometimes gets recomputed twice or even thrice during a single visit to the /friends
page. Oddly enough, if it recalculates twice, the database queries work fine! But with three recalculations, the first DB access fails for some reason while the subsequent two succeed.
During the failure, this stack trace is revealed:
// NOTE: imsolonely is one of the users that should be returned in the friendRequests
imsolonely's public key: undefined
debug.js:41 Exception in template helper: TypeError: Cannot read property 'profile' of undefined
at Object.Utils.getPublicKeyByUsername (http://localhost:3000/lib/utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:79:22)
at Object.Utils.getFingerprintByUsername (http://localhost:3000/lib/utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:88:24)
at http://localhost:3000/client/friends.js?dbec4a7537c9d0abf56a74489824969cb7baadfe:25:35
at Array.forEach (native)
at Function._.each._.forEach (http://localhost:3000/packages/underscore.js?0a80a8623e1b40b5df5a05582f288ddd586eaa18:156:11)
at Object.Template.friendRequests.helpers.friendRequests (http://localhost:3000/client/friends.js?dbec4a7537c9d0abf56a74489824969cb7baadfe:22:7)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2693:16
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1602:16
at Object.Spacebars.dot (http://localhost:3000/packages/spacebars.js?3c496d2950151d744a8574297b46d2763a123bdf:231:13)
at http://localhost:3000/client/template.friends.js?a2da726f6dad1aaecfdedfe216aa3378fff938b5:24:37
utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:78 imsolonely's public key: {"profile":{"publicKey":"0404b4880129edc1ea2652dd1eff1c8728269874b9b0ace02cc90edcb449c3f3d716c2f8b79a5fe5695d52cd85aed228f977073538625e8e71f1cfd764766669b1"},"_id":"sXjzt7YHA8KTyAib5"}
utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:78 imsolonely's public key: {"profile":{"publicKey":"0404b4880129edc1ea2652dd1eff1c8728269874b9b0ace02cc90edcb449c3f3d716c2f8b79a5fe5695d52cd85aed228f977073538625e8e71f1cfd764766669b1"},"_id":"sXjzt7YHA8KTyAib5"}
This issue extends to several, if not all, of my helpers. They're being called even when they shouldn't be, especially when the user isn't logged in (and therefore, the helper shouldn't need to recalculate).
Take a look at the relevant code snippets:
client/friends.js
:
Template.friendRequests.helpers({
friendRequests: function () {
// Problem 1: The template gets called right after I log in but before I am fully logged in
// so I need this call here.
if(!Meteor.user())
return Utils.notLoggedInErrorMsg;
// Problem 2: The template gets called three times. The first time fails the DB query
// as if the DB row did not exist. The next 2 times it succeeds. But it should only be
// called once.
var reqs = Friends.getFriendRequests(Utils.me());
_.each(reqs, function(element, it, list) {
check(element.initiator, String);
// Get the user's fingerprint
element.fingerprint = Utils.getFingerprintByUsername(element.initiator);
});
return reqs;
},
});
client/friends.html
:
<template name="friends">
{{> friendRequests}}
{{> searchForm}}
<h2>My friends</h2>
<ul>
{{#each friends}}
<li>{{this}}</li>
{{/each}}
</ul>
</template>
<template name="friendRequests">
<h2>Friend requests</h2>
{{#if friendRequests.length}}
<p>Someone's popular today!</p>
<ul>
{{#each friendRequests}}
<li><b>{{initiator}}</b> with fingerprint <pre style="display: inline">{{fingerprint}}</pre> sent you a request on <em>{{date}}</em>. <a href="#">Accept <b>{{initiator}}</b> as a friend?</a></li>
{{/each}}
</ul>
{{else}}
<p>Sorry, nobody likes you right now.</p>
{{/if}}
</template>
lib/utils.js
:
Utils = {
// ...
// other stuff
// ...
// @return a hex-encoded public key as a string
getPublicKeyByUsername: function (username) {
var user = Meteor.users.findOne({ username: username }, { fields: { 'profile.publicKey': 1 } });
console.log(username + '\'s public key: ' + EJSON.stringify(user));
var pubKey = user.profile.publicKey;
return pubKey;
},
// NOTE: not used yet, i used the CryptoUtils function directly when I needed it
//
// @return the fingerprint as a hex-encoded string
getFingerprintByUsername: function (username) {
var pubKey = Utils.getPublicKeyByUsername(username);
var fingerprint = CryptoUtils.getPublicKeyFingerprint(pubKey);
return fingerprint;
},
notLoggedInErrorMsg: 'Meteor is being silly and calling this when I\'m not logged in.',
}
If relevant, I'm utilizing the iron:router
1.0.3 package to navigate to the /friends
URL.
Any insights into why the friendRequests
helper keeps recalculating even when it's out of sight, and why it's triggered multiple times - rather than just once - upon refreshing the /friends
page would be highly appreciated!
Sincerely, Alin