Meteor helpers run multiple times


#1

Helpers run multiple times (on the same page), when session changes (they’re depending on sessions since that’s how I chose which data to load)

  1. I get the data from JSON (Meteor.http.get…) *Check the server methods

  2. Api key is stored on user (useraccounts package, currently working as expected)


API Key endpoints (if needed) : https://keeky.github.io/Guild-Wars-2-API-Explorer/#v2/account
Meteorpad : 6mY7c6yf8pAjACyZe/Leaderboard (haven’t checked if it’s working there)
http://webm.host/b8c71/vid.webm
CLIENT SIDE

myprofile.html*

<template name="myprofile">
      <ul class="nav nav-tabs nav-justified" style="margin-bottom: 15px;">
          {{#each characterlist}}
        <li><a name="{{this}}" class="charactername" href="#">{{this}}</a></li>
          {{/each}}
    </ul>
    <div class="container">
    <div class="well">
      <h1 class="text-center">{{character_selected_name}}</h1>
      <h6 class="text-center">Level {{character_selected_data.level}} {{character_selected_data.profession}}</h6>
      <hr>
    </div>
    <div class="well">
      <h2 class="text-center">Bags</h2>
      <h5 class="text-center">
        {{#each character_selected_data.bags}}
        {{id}}
        {{/each}}
      </h5>
      <hr>
    </div>
    </div>
    <div class="footer-myprofile">
      <div class="row whitetext text-center boldtext">
        <div class="col-md-4">
          {{basicinfo.name}}
        </div>
        <div class="col-md-4">
          {{basicinfo.world}}
        </div>
        <div class="col-md-4">
          {{#each basicinfo.guilds }}
           {{this}}
          {{/each}}
        </div>
      </div>
    </div>
    </template>

myprofile_helpers.js

Template.myprofile.helpers({
  "basicinfo" : function(){
    Meteor.call("getmyprofilebasic", function(error,result){
      result.world = worlddata.findOne({"worldid":result.world}).name;
      Session.set("basicinfo",result);
    });
    return Session.get("basicinfo")
  },
  "characterlist" : function(){
    Meteor.call("getmyprofilecharacters", function(error,result){
      result = result.sort();
      Session.set("characterlist",result);
      Session.set("character_selected",result[0])
      console.log(result);
    });
    return Session.get("characterlist")
  },
  "character_selected_name": function(){
    return Session.get("character_selected")
  },
  "character_selected_data": function(){
    Meteor.call("getmyprofilecharacterdata",Session.get("character_selected"), function(error,result){
        console.log(result);
      Session.set("character_selected_data",result);
    });
      return Session.get("character_selected_data")
  }
});

myprofile_events.js

Template.myprofile.events({
  "click .charactername" : function(event,template){
    Session.set("character_selected",this.toString())
    event.preventDefault();
    return false
  }
});

SERVER SIDE
Server methods (myprofile)

Meteor.users.deny({
  update: function() {
    return true;
  }
});

if (Meteor.isServer){
  Meteor.methods({
    "getmyprofilebasic" : function(){
      this.unblock();
      var userid = this.userId;
      console.log(userid);
      var apikey = Meteor.users.findOne({_id:userid}).profile.apikey;
      var url="https://api.guildwars2.com/v2/account?access_token=" + apikey;
			var result = Meteor.http.get(url, {timeout:30000});
			if(result.statusCode==200) {
				var respJson = JSON.parse(result.content);
				return respJson;
			} else {
				console.log("Response issue: ", result.statusCode);
				var errorJson = JSON.parse(result.content);
				throw new Meteor.Error(result.statusCode, errorJson.error);
			}
    },
    "getmyprofilecharacters" : function(){
      this.unblock();
     var userid = this.userId;
     var apikey = Meteor.users.findOne({_id:userid}).profile.apikey;
     var url="https://api.guildwars2.com/v2/characters?access_token=" + apikey;
     var result = Meteor.http.get(url, {timeout:30000});
     if(result.statusCode==200) {
       var respJson = JSON.parse(result.content);
       return respJson;
     } else {
       console.log("Response issue: ", result.statusCode);
       var errorJson = JSON.parse(result.content);
       throw new Meteor.Error(result.statusCode, errorJson.error);
     }
   },
   "getmyprofilecharacterdata" : function(name){
     this.unblock();
    var userid = this.userId;
    var apikey = Meteor.users.findOne({_id:userid}).profile.apikey;
    var url="https://api.guildwars2.com/v2/characters?access_token=" + apikey +"&ids=" +name;
    var result = Meteor.http.get(url, {timeout:30000});
    if(result.statusCode==200) {
      var respJson = JSON.parse(result.content);
      return respJson[0];
    } else {
      console.log("Response issue: ", result.statusCode);
      var errorJson = JSON.parse(result.content);
      throw new Meteor.Error(result.statusCode, errorJson.error);
    }
   }
  });
}

Meteor methods (getitemicon)

Meteor.methods({
  "getitemicon" : function(id){
    this.unblock();
    var url="https://api.guildwars2.com/v2/items?ids=" + id;
    var result = Meteor.http.get(url, {timeout:30000});
    if(result.statusCode==200) {
      var respJson = JSON.parse(result.content);
      return respJson[0].icon;
    } else {
      console.log("Response issue: ", result.statusCode);
      var errorJson = JSON.parse(result.content);
      throw new Meteor.Error(result.statusCode, errorJson.error);
  }
  }
});
Meteor.call("getitemicon",8932);

#2

I only had a quick look at the code, but it looks like you’re using a meteor method call within template helpers to create the data scope for a helper.

I have found a much better way to handle this is to create a reactive var (so add the reactive var package). Then have a function to store the scope of that reactive var on template creation… Template.myprofile.onCreated(...). You can use two reactive vars, one to store the data ID you’re querying, and the other can be an object full of the data. Since it is reactive if you change the data ID, the function should request a new data object.

Then within the template you just reference the reactive var with Template.instance().reactiveVarNameHere.get().datafield (whatever the name and data field you’re after).

You can even use a helper within a {{#with}} block to just set the scope for a block within the template. So that you don’t need to continually use Template.instance... etc…

edit: Just realised that I totally ignored the question about the helper running multiple times. Since you’re using sessions and the method calls are delayed, I am guessing that it just sets off a chain of events where it needs to re-run a bunch of your helpers…