Track template state the template.instance scoped reactive way instead of using Session vars


#1

I see a lot of questions here and on stackoverflow related to ‘passing data between templates’. Nearly always the suggested solution is to use Session. I almost always avoid Session. I think it pollutes the global scope, it prevents you from running multiple instances of the template, and it leads to undisciplined separation of concerns.

I came across this interesting example which uses Session to preserve template state and rewrite it to work without the Session vars.

Attach a reactiveDict to the template instance onCreate. Use this to store state instead of global Session var!

Template.Restaurants.onCreated(function() {
  this.state = new ReactiveDict;
  this.state.set('currentRestaurant', null); // could set a default value here
});

this event handler will set the state of the reactiveDict on click

'click': function(e, t) {
    t.state.set('currentRestaurant', this._id);
}

this helper is used to show/hide the menu template

currentRestaurant: function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Template.instance().state.equals("currentRestaurant", this._id);
},

menu template receives the selected id from data context instead of from Session

<template name="Restaurants">
  <ul>
    {{#each Restaurant}}
    <li>
      {{name}}
      {{#if currentRestaurant}}
      {{> menuItems restaurant=_id}}
      {{/if}}
      </li>
    {{/each}}
  </ul>
</template>

<template name="menuItems">
  <ul>
    <li class="menu">I'm a menu for {{restaurantName}}!</li>
  </ul>
</template>

added this helper just to show we really got the id

Template.menuItems.helpers({
  restaurantName: function() {
    var restaurantMenu = Restaurants.find(this.restaurant).fetch();
    return restaurantMenu[0].name;
  },
})

Posted a fully working project to github.

App is hosted on meteor.com
http://scopedreactitivydemo.meteor.com/


#2

Hum, imho you are not solving anything.
You are only permitting your child template to access parent context, which has always been possible (one way or another).
The difficulties lies when your two templates are not related, then your solution is not useful.


#3

@vjau, I think you are half-correct. I think in this current example app, he is not solving anything. But what he has done is he has shown a way of reusing code without having to hardcode Session variable names. That is, if his code was more generic, this would be useful. Maybe something like:

<head>
  <title>Test</title>
</head>

<body>
{{> ClickableList srcCollection='Restaurants'}}
{{> ClickableList srcCollection='Chefs'}}
{{> ClickableList srcCollection='SomethingElse'}}
</body>

<template name="ClickableList">
  <ul>
    {{#each iterateCollection}}
    <li>
      {{name}}
      {{#if selectedItem}}
      {{> itemDetails}}
      {{/if}}
    </li>
    {{/each}}
  </ul>
</template>

<template name="itemDetails">
  <ul>
    <li class="details">{{itemContent}}</li>
  </ul>
</template>
// helpers.js
Template.ClickableList.helpers({
  iterateCollection: function() {
    // Need dburles:mongo-collection-instances for the following line
    return Meteor.Collection.get(this.srcCollection).find();
  },

  selectedItem: function() {
    return Template.instance().state.equals("selectedItem", this._id);
  },

});

Template.menuItems.helpers({
  itemContent: function() {
    // You can do this instead of querying database again. Just get the context of the parent.
    var item = Template.parentData(1);
    return content;
  },
})

// onCreated.js
Template.ClickableList.onCreated(function() {

  this.state = new ReactiveDict;
  this.state.set('selectedItem', null);

});

// events.js
Template.ClickableList.events({

  'click': function(e, t) {
    t.state.set('selectedItem', this._id);
    console.count(t.state.get('selectedItem'));
  }
});

Now, instead of using a different Session variable name for each and every instance of this template, the reactive dict is baked into the template instance and he can keep track of state reactively without the Session variable (which is in the global scope). This is a better way of doing things in terms of code reusability.


#4

Yes perhaps i need to work up a better example. I was just responding to this answer which used Session vars and decided to rewrite it without session vars.

On the whole in a tiny app it often doesn’t matter, which is why everyone uses it. But the Session object is a global (singleton) registry. There are lots of reasons not use them (Just google “why (singleton, global objects, registry pattern) (is|are) bad”.

Why do we see people using Session like crazy in example after example? Because Meteor tries hard to be very friendly for beginners and the Session object is a very easy (lazy) way to store variables reactively. Fine for demos and small apps, but a terrible idea in anything large-scale. Assuming you didn’t need the reactivity, would you be putting so much data in a global variable throughout your application? If you’re an experienced developer, probably not. If you do need the reactivity, Meteor provides all you need to create you own reactive variables.

Using Session to get the contents of a variable from one place in your code to another one (being a universal, shared dumping ground to “bridge” otherwise unrelated objects) is a misuse. This is neither the “Meteor” way nor clean use at all (referring to the search results above). It’s just a quick and dirty way.


#5

I think you are hallucinating a Session variable where none exists mate.


#6

Nope. I was saying your method lets you avoid using a different Session variable for many instances of a single Template. I was agreeing with you. :wink:


#7

Yes. You could also just pass the name. The point was just to show that the id was passed and could be used to query the data you need i.e. customers (list) and customer (profile) Perhaps I should setup a second collection for the “menuItems” so people don’t get the wrong idea.


#8

ah ok! Cheers! :wink: yes the whole point is to avoid Session.


#9

I don’t contest the fact that Session is bad and should be avoided for medium to big sized app.
I know that global objects that everyone can modify are bad also (i think it’s called common coupling).
However even with best decoupled design (which Meteor doesn’t encourage…), there is a point where you need a central point of communication. Attaching some state to templates (which can be useful for customized templates multi-inclusion) doesn’t seems to be a solution for this problem of central communication between foreign templates, or at least i did not understand it like that.


#10

I thought I read somewhere that in order for the ReactiveDict to persist through a HCR, you have to give it a name when initializing it? Maybe that was just for ReactiveVar.


#11

oh is that right? so adding a variable to an object (adding “state” to “this”) isn’t good enough?

so instead of

this.state = new ReactiveDict;

it should be like this?

var templateState = new ReactiveDict;
this.state = templateState;

#12

Yes, it’s mostly meant as a way to store template state. I’ve seen a lot of contractors using module level scoped variables to keep track of all sorts of things (like processing, warning, editing) and to share state between events and helpers. We now separate event and helper code into different files, which discourages the sloppy use of module level scope :wink: template.instance is one way for state to be shared between helpers and event code. The scope is just perfect for that.

how we organize files

[client]
|--[views]
|----[postsList]
|------events.js
|------helpers.js
|------templates.html
|------styles.less
|------onRendered.js
|------routers.js

#13

this is what I was referring to:


#14

it’s a bit ambiguous isn’t it? Does it mean reactivedict var names need to be globally unique? does attached the var to your template make it unique?

this.state = new ReactiveDict;
this.state.set('currentRestaurant', null);

#15

I think it means that it just needs a name like a collection would. Like it says at the bottom, it is “reminiscent of Collections”

Something like this:

Template.myTemplate.onCreated(function() {
    this.state = new ReactiveDict('uniqueName');
})

I haven’t tested it though so I’m not totally sure. Maybe I’ll whip up a MeteorPad and link it here.


#16

It is just save current dict values for hot code push.That’s all!


#17

@mrzafod but if attached to template then you can use it like Session for template state, but with reduced scope. that is good.

like this demo:
http://drilldowndemo.meteor.com/


#18

@maxhodges thats right! But we talk about ‘named’ ReactiveDict - it means that its key-values are preserved for hot code push. Besides you can’t define ReactiveDict with the same name at the time.


#19

So what does that mean exactly? if I have the same named values in two different template scoped reactivedicts, it will blow up?

Template.A.onCreated(function() {
this.state = new ReactiveDict;
this.state.set('foo', bar);

Template.B.onCreated(function() {
this.state = new ReactiveDict;
this.state.set('foo', barbar);

#20

It means you can’t do this one

Template.A.onCreated(function() {
    this.state = new ReactiveDict('myNamedDict');
    this.state.set('foo', bar);
})
        
Template.B.onCreated(function() {
    this.state = new ReactiveDict('myNamedDict');
    this.state.set('foo', barbar);
})

And even you can’t render Template.A twice, because you can’t instantinate new named ReactiveDict! source link