Meteor - ReactiveDict

meteor

http://meteorcapture.com/a-look-at-local-template-state/
http://docs.meteor.com/#tracker
https://meteorhacks.com/journey-into-meteors-reactivity/
https://www.discovermeteor.com/blog/reactivity-basics-meteors-magic-demystified/ - done reading
http://info.meteor.com/blog/the-meteor-chef-reactive-dict-reactive-vars-session-variables
https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
http://richsilv.github.io/meteor/meteor-reactive-data-types/
https://docs.meteor.com/api/tracker.html

What is ReactiveDict?

In this step, we'll add a client-side data filtering feature to our app, so that users can check a box to only see incomplete tasks. We're going to learn how to use a ReactiveDict to store temporary reactive state on the client. A ReactiveDict is like a normal JS object with keys and values, but with built-in reactivity.

      <label class="hide-completed">
        <input type="checkbox" />
        Hide Completed Tasks
      </label>

      <form class="new-task">
        <input type="text" name="text" placeholder="Type to add new tasks" />
      </form>

Now we need to add the reactive-dict package:

meteor add reactive-dict

Then we need to set up a new ReactiveDict and attach it to the body template instance (as this is where we'll store the checkbox's state) when it is first created:

import { ReactiveDict } from 'meteor/reactive-dict';

Template.body.onCreated(function bodyOnCreated() {
  this.state = new ReactiveDict();
});

Then, we need an event handler to update the ReactiveDict variable when the checkbox is checked or unchecked. An event handler takes two arguments, the second of which is the same template instance which was this in the onCreated callback:

  'change .hide-completed input'(event, instance) {
    instance.state.set('hideCompleted', event.target.checked);
  },

Now, we need to update Template.body.helpers. The code below has a new if block to filter the tasks if the checkbox is checked:

Template.body.helpers({
  tasks() {
    const instance = Template.instance();

    if (instance.state.get('hideCompleted')) {
      // If hide completed is checked, filter tasks
      return Tasks.find({ checked: { $ne: true } }, { sort: { createdAt: -1 } });
    }

    // Otherwise, return all of the tasks
    return Tasks.find({}, { sort: { createdAt: -1 } });
  },
});

Now if you check the box, the task list will only show tasks that haven't been completed. ReactiveDicts are reactive data stores for the client.

Until now, we have stored all of our state in collections, and the view updated automatically when we modified the data inside these collections. This is because Mongo.Collection is recognized by Meteor as a reactive data source, meaning Meteor knows when the data inside has changed. ReactiveDict is the same way, but is not synced with the server like collections are. This makes a ReactiveDict a convenient place to store temporary UI state like the checkbox above. Just like with collections, we don't have to write any extra code for the template to update when the ReactiveDict variable changes — just calling instance.state.get(…) inside the helper is enough.

How can we use reactive dict for states?

The reactive-dict package lets you define a simple reactive key-value dictionary. It’s a convenient way to attach internal state to a component. We create the state dictionary in the onCreated callback, and attach it to the template instance:

Template.Lists_show.onCreated(function() {
  this.state = new ReactiveDict();
  this.state.setDefault({
    editing: false,
    editingTodo: false
  });
});

Once the state dictionary has been created we can access it from helpers and modify it in event handlers (see the code snippet above).

How can we turn off reactivity?

You can put your html containing this into a {{#constant}} block:

Disable reactivity in your template helper:

Template.home.mydata = function() { return MyCollection.find({}, {reactive:false}) };

Using cancel inside the event handler after the call to the methods cancels the chain of reactivity.

What is a reactive context?

Reactivity is built into Meteor and you get it for free, keep in mind that reactivity will only be used if you set up your code to so. Meteor provides reactive contexts in which reactivity takes place. These contexts can be created by using one of the following:

  1. Template
  2. Blaze.render and Blaze.renderWithData
  3. Tracker.autorun

What are the rules for reactivity?

  1. Using a reactive data source inside a computation creates a dependency, so that when the data is changed, the function is re-executed.
  2. Functions inside reactive contexts are called computation
Template.friendHouse.helpers({
  waterTheFlowers: function() {
    var day = Session.get('today');
    if (day === 'Monday') {
      ...
    }
  }
});

In the above code, 'Template' creates a reactive context, 'waterTheFlowers' is a computation, and Session is a reactive data source that will invalidate the computation when its contents change.

What is a computation?

Functions that are inside a reactive context are called computations.

Template.friendHouse.helpers({
  waterTheFlowers: function() {
    var day = Session.get('today');
    if (day === 'Monday') {
      ...
    }
  }
});

In the above code, 'Template' creates a reactive context, 'waterTheFlowers' is a computation, and Session is a reactive data source that will invalidate the computation when its contents change.

Reactive data sources invalidate computations when data changes, which causes computations to reexecute. All reactive data sources used inside a computation are automatically associated with the computation.

When should we use the Session object?

In Meteor, Session should be used to store volatile (non-persistent) information in memory on the client side. Session is a reactive dictionary.

What is the purpose of the setDefault() method of the Session object?

Sets a value for a key only if it is undefined.

Session.setDefault("key","default value");
Session.get("key");
Session.set("key","new value");
Session.equals("key","expression");

Can we use Session to hold arrays or objects?

Yes. Although Session is typically used with strings, it can also hold arrays or objects.

How can we use Tracker.autorun?

Tracker.autorun(function(){
  console.log("The selectedHouse ID: " + Session.get("selectedHouseId");
});

Tracker.autorun set up the reactive context for the specified function (computation), and Session is a reactive data source inside the computation. Anytime, Session is change, this computation is re-executed.

How can we use ReactiveVar?

ReactiveVar is part of Meteor's core but we must add the package:

meteor add reactive-var

You declare them with new ReactiveVar(), and then use them with get() and set() just like Session variables:

var count = new ReactiveVar(0);
count.set(1);
count.get(); // 1

What are the differences between ReactiveVar and Session?

Both ReactiveVar and Session use get() and set() functions, but ReactiveVar does not pollute the global namespace and can be limited to a local scope. You can reuse it with different values for the same template. Just like Session, ReactiveVar stores key-value pairs. It may store whole objects as values. Updating objects inside the ReactiveVar container requires the use of set(). Because it is scoped to a template context, you must declare a ReactiveVar inside the onCreated callback for a template. There is no setDefault(), but you can pass default value to ReactiveVar when a new instance is declared:

Template.templateName.onCreated(function(){
  this.watered = new ReactiveVar();
  this.watered.set(false);
});

Here the keyword this refers to the currently available data context

What is a reactive computations?

Of course, no matter how loud your dog barks, it will all be for nothing if you’re not home to hear it. Similarly, reactive data sources need something to “listen” to them when they signal a change. This is where reactive computations come in (also known as reactive contexts). A reactive computation is simply a block of code (basically, the inside of a function) that will re-run whenever a reactive data source inside it changes.

Can we create our own reactive data sources and reactive computation?

Yes. We can also create our own custom reactive sources and computations.

  1. ReactiveVar: Custom Reactive Sources. The optional reactive-var package makes it possible to define your own, custom reactive variables. These work just like Session variables, except they’re not global. ReactiveVars don’t have global names, like the “foo” in Session.get(“foo”). Instead, they may be created and used locally, for example attached to a template instance, as in: this.foo.get().

How can we create a reactive computation?

Just like we can have custom reactive sources, we can also have custom reactive computations. Tracker.autorun() lets you define an arbitrary block of code that will run every time any one of its reactive sources change, anywhere in your app:

Tracker.autorun(function () {
  var count = Session.get('count');
  console.log('Autorun is auto-running!');
  console.log(count);
});

Can we put Tracker.autorun anywhere?

Yes, but usually we should put it inside the template's onCreated callback so that we the template is destroyed, the autorun is also removed.

What is the definition of reactive composition?

One nifty property of reactive data sources is that reactivity can be passed on through composition, the act of wrapping a function inside another one. So in this example:

var getCount = function () {
  return Session.get('count')
}

The getCount function is actually a reactive data source too, just by virtue of calling Session.get('count'), which is itself a reactive data source. So in other words, we can rewrite this:

Template.hello.helpers({
  counter: function () {
    console.log('counter helper is running')
    return Session.get('count');
  }
});

As this:

var getCount = function () {
  return Session.get('count');
}

Template.hello.helpers({
  counter: function () {
    console.log('counter helper is running')
    return getCount();
  }
});

And in both case, the counter helper will re-run whenever the count session variable changes. But be careful! Composition applies only to functions, not variables. Which means that this would not work:

var myCount = Session.get('count');

Template.hello.helpers({
  counter: function () { // will not re-run
    console.log('counter helper is running')
    return myCount;
  }
});
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License