Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meteor: using Sessions and ReactiveVar

Tags:

meteor

When should you use Session and ReactiveVar? I use Session variable as a communication medium between components. Let's look at Stackoverflow for example.

enter image description here

I labeled three hypothetical components. Let's look at the filters component. If you click Tags then the Main components will show its questions based on your favorite tags. To do this, I will set Session.set("Main/showTags", true) when the Tags button is clicked. In the Main component, I will have a helper function like below:

Template.Main.helpers({
  posts: function() {
    var isTags = Session.get("Main/showTags");
    var isQuestions = Session.get("Main/showQuestions");
    ...
    if (isTags) {
      return Posts.find().sort({tags: 1}) // or something along the lines
    } else if (isQuestions) ...
  }
});

This has worked well in most cases but I've seen from a lot of places that I should refrain from using Session and use ReactiveVar. But If I use ReactiveVar everywhere, then I would need the reference to all template instances. I expect it to work well between direct parent and children templates (ex. inside the main component, there could be VoteAnswerViewsButtonTemplate) but how would you do it with ReactiveVar when you want independent components to talk to each other?

Here's my final question. How can I appropriately use Session and ReactiveVar to keep the scope of components and make them communicate with each other as well? Also, if I use Session the way I do now, am I polluting the global namespace unnecessarily?

Related Documents:

  1. https://dweldon.silvrback.com/scoped-reactivity
  2. https://www.discovermeteor.com/blog/reactivity-basics-meteors-magic-demystified/
like image 778
Maximus S Avatar asked Jul 23 '15 06:07

Maximus S


3 Answers

As far as I know, there is no built-in features related to Session variables that differentiate them from a regular reactive dictionary global variable (like @Kyll stated) that you would declare, for instance, in your client.js file. The only difference is the Session "limitation" of being necessary accessible application wide.

I am very pleased to take advantage of this difference, when I use a reactive dictionary or reactive variables in a smaller scope. I consider I have three kind of scopes:

1 - Global scope. E.g. the current UI language, the UI skin. I use Session for that. Simple and global data, not the kind that could be confused with anything else.

2 - A cluster of templates. Let's say, for example, that I create a page to generate and customize pdfs in my app. I will not reuse any of the components elsewhere. My cluster is a folder with three files, let's call them pdfgenerator.html, pdfgenerator.js and pdfgenerator_controller.js.

I use pdfgenerator_controller.js to extend the route with all the specifics.

In the pdfgenerator.js file, I have several templates that I all use in the cluster. At the beginning of the file, I create a reactive dictionary pageSession (similar to reactive variables) and I use it in all my cluster. It allows me to pass data among all my components.

3 - Local scope. Whether it is a single template or a reusable component, it is meant to work alone. I won't use Session vars for those either. I do not want to overcrowd the Session name space. What I do is pass to my template every data I need to operate it during its instantiation.

It could be from Spacebars:

{{> mySingleTemplate data=myData}}

or using Javascript:

Blaze.renderWithData(Template.mySingleTemplate , myData, self.firstNode);

In the local scope case, I also use a reactive dictionary or reactive vars just to handle the reactivity happening within the single template. In this case, I try to avoid the situations where I need to reactively return data to the parent template. If I have to (i.e. I won't probably make a package out of it), I use a local minimongo collection declared as global in the scope of the parent template. This way, I can pass along informations from the reusable component to its parent.

Example: an upload template. I use minimongo to store the name, size, type, status and url of each uploaded file. The minimongo collection is shared between the parent form template and the child uploader template.


Bottom line: I only use Session variables for basic and global information. If the data structure I need to share globally is too complex/large, I rely on a collection.

I am curious to know if I get it right, so this is as much an answer than a test to see if people agree with my way of doing things. All comments and advices are welcome.

like image 62
Billybobbonnet Avatar answered Nov 07 '22 10:11

Billybobbonnet


Session vars get a bad rap. The truth is, they're just fine until your app gets big. You'll know when it's time to move away from Session vars when:

  • You can't keep the var names straight (eg wtf does isColumnHidden mean again?) Or, the session vars form natural clusters (3 session vars for tooltips, 5 for tags, etc.).
  • You want that Session info in the url so you can send someone a link & they see what you see

So, how do you resolve each problem?

  • For the first example, create a package. For example, In one of my larger projects I create a rightSideMenu and leftSideMenu package. Each has their own ReactiveDict that I export to the global scope (eg rightMenu.RD.get('col1Hidden')). This keeps the approach modular. I can completely rewrite my menu code, but as long as I still expose the ReactiveDict, the API stays the same. That said, I still use a Session var to show/hide the left & right menus. Why? because Session is the perfect use for menus. I don't want a hidden menu to persist through browser sessions (ever close something you didn't know how to reopen?), but I do want it to persist through routes. In other words, I want it to last for that browsing Session.

  • If you want the url to hold session info, you need to use params or query params. For example, if you've got a map with 100 markers & the user wants to send that page to his buddy with a specific marker selected, it makes sense that the url be something like url.com/marker20. But why stop there? You could also include the lat & lng of the map center: url.com/marker/40.23432/122.2342. Maybe that's too much, maybe not. You decide. In your example, this makes a LOT more sense than storing isTag in a session var because it lets folks bookmark it, use the back button, share it, and change it without using their mouse (yes, your userbase is as lazy as I am).

An additional note on your current setup is that you're using flags for something that is mutually exclusive, leading to a conditional hell. If you've got 2 vars that can't both be true, stop & rethink the design. So, if I HAD to use session vars, I'd do something like:

 currentTemplate = Session.get('filter') 
 {{Template.dynamic template=currentTemplate}}

In the end, you'll need to think about a lot more than just ReactiveVariables. What about dependencies? Not every module needs to access dependencies of every other module. Same goes for collections, methods, and even CSS. I say let it grow organically & when it hits you "hey, this is a component" then turn it into a package, export a variable, and keep it modular. Until that point, Session vars are fine.

like image 5
Matt K Avatar answered Nov 07 '22 10:11

Matt K


Your issue is not polluting the global scope. Then why not make your own?

myComponentScope = {}; //Declare a global scope for this component

Then just place as many things as you want inside. For example,

myComponentScope.foo = new Reactive-Var('foo');

And if you want something that looks like Session you can add Reactive-Dict:

meteor add reactive-dict

And use it this way:

myComponentScope = new Reactive-Dict(/* Optional name */);

The optional name makes the dictionary persistent across hot-code pushes.
The API for this dictionary is the same as Session.


Thus, when you have something strictly local (one template), go for a Reactive-Var.
When it's something multiple templates of the same kind share, go for your own scope.
If it's something so important all your application will need it, use Session.

like image 3
Kyll Avatar answered Nov 07 '22 11:11

Kyll