I want to add a logout button to my application but that turns out to be more difficult than I thought because there's a situation in which a helper is called unexpectedly after calling Meteor.logout
. Consider the following simple application (you can find the complete code in this MeteorPad; I tried to keep it as short as possible):
The server publishes the contents of a Tasks
collection if the user is logged in. Otherwise, it publishes no records.
Meteor.publish('tasks', function() {
if (this.userId) {
return Tasks.find();
} else {
return null;
}
});
There's a layout template that handles the login/logout, subscribes to the publication, and displays a sub template (task
):
<template name="layout">
{{#if loggedInAndReady}}
{{> task}}
<button class="logout">Logout</button>
{{else}}
<button class="login">Login</button>
{{/if}}
</template>
Inside this task
template, there is a helper title
that retrieves a task from the subscription using Tasks.findOne()
and writes to the log when it's called:
<template name="task">
{{description}}
</template>
Template.task.helpers({
title: function() {
console.log("task helper");
Tasks.findOne();
}
});
Here is the problem: When I log out, loggedInAndReady
will become false but the title
helper of the task
template is still called. However, I don't want the helper to be called, because I made the assumption that the data I'm trying to get in my helpers always exits. This assumption is always true, except for the short moment between the logout and the removal of the template.
These are the steps that happen when you log in and out again (you can see this output in the development console of the MeteorPad linked above):
task template created
task helper
logging out
task helper <-- Why is this called? I'm already logged out.
task template destroyed
I know that when the user is logged out, the tasks
publication on the server is executed again with null
as the new user ID, which, in turn, causes the helper on the client to run again because the result set changed (i.e., became empty). However, it's already known at this point that the result of the helper won't be used anymore (the template is destroyed afterwards).
The strange thing is that when you log in, reload the page, and then log out, it works as expected (the helper is not called again):
task template created
task helper
logging out
task template destroyed
Am I misunderstanding a part of Meteor's reactivity concept or is there a mistake in the code? How can a page reload affect the execution of a helper like this?
You have a race condition where the round trip for for your user is taking slightly longer than the data for your task. You'll find that Meteor.userId()
is actually faster to respond than Meteor.user()
because (I believe) it doesn't require a second round-trip.
However, none of this really matters because you simply need to add a guard to your helper. Helper functions need to be resilient to shifts in their underlying data, so you should just rewrite it like so:
Template.task.helpers({
title: function() {
var task = Tasks.findOne();
return task && task.title;
}
});