3
votes

The following simple function on a template will usually fail on me because the user object is undefined, even though I'm logged in:

Template.hello.userData = function() {
  var user = Meteor.users.findOne(Meteor.userId());
  return user.emails[0].address;
};

It's easy to duplicate:

  • on the command-line:
    • meteor create test-user
    • cd test-user
    • meteor add accounts-password
    • meteor add accounts-ui
  • edit test-user.js and add the 'userData' code (above) into the isClient block
  • edit test-user.html and add inside 'hello' template: {{loginButtons}} {{userData}}
  • go to the app and create an account, which signs you in and you get an error in the console: Uncaught TypeError: Cannot read property 'emails' of undefined

The insidious thing is the "usually" part: it sometimes succeeds, so there may be a race condition involved... and it may be something with the order that dependencies are loaded.

I actually hit this in a more complicated app, trying to access the 'profile' property of my logged-in user (which exists, I promise). It gives slightly different errors messages in different browsers; here's the stack trace in Chrome:

Exception from Deps recompute: TypeError: Cannot read property 'profile' of undefined
    at Object.Template.workbook.owner (http://localhost:3000/client/client.js?153b3db62478692678dd9fdf9f1a9dd0b6b6a76e:130:21)
    at apply (http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:276:24)
    at invoke (http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:301:12)
    at http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:365:30
    at Object.Spark.labelBranch (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1171:14)
    at branch (http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:355:20)
    at http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:364:18
    at Array.forEach (native)
    at Function._.each._.forEach (http://localhost:3000/packages/underscore.js?13ab483e8a3c795d9991577e65e811cd0b827997:130:11)
    at template (http://localhost:3000/packages/handlebars.js?c2b75d49875b4cfcc7544447aad117fd81fccf3b:358:7) debug.js:41
Exception from Deps recompute: Error: Can't create second landmark in same branch
    at Object.Spark.createLandmark (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1226:13)
    ...<snip>...

That last error about Can't create second landmark in same branch send me on a chase through other posts that all had to do with loops, but this is just a simple collection access. There are some forEach calls in the stack trace, so I tried solutions like those in Meteor issue 281, to no avail.

Hints and explanations of Meteor conventions are much appreciated. Thanks.

3

3 Answers

5
votes

I was experiencing this same issue.

return user && user.emails[0].address;

Checking if user is undefined first ( user && ... ) seems to have done it.

2
votes

You are setting up a race condition between the template being rendered and the data arriving from the server.

This is similar to expecting data to be available immediately after an async call; you have to wait for the callback to be executed to access the response. In Meteor's case, since it takes some time to retrieve the data from the server, you have to ensure the expected fields have been populated before using them, as @landland suggests.

To explain the race condition further, most of the time your data arrives before the template is rendered and everything is fine. But sometimes, the server hasn't finished publishing the user data to the client by the time the template renders so you get the error. (This is what @jrullmann was describing in his comment.)

Also, since in your full app you are accessing a user different than your logged in one, you need to explicitly publish that user's information. Only the currently logged-in user's info is auto-published by the Accounts packages.

1
votes

Have a look at Meteor user http://docs.meteor.com/#meteor_user

Meteor already provides the logged in user for you so you don't have to find him again.

Try

Template.hello.userData = function() {
  var user = Meteor.user();
  return user && user.emails[0].address;
};