As we all know, magic can be used both for good (Penn & Teller) or bad (Saruman). And as countless folk tales have made clear, things can go very wrong when you try wielding powers that you don’t totally understand.

Meteor can sometimes be the same way: you’re happily coding along and the next thing you know, you’ve triggered an infinite reactive loop and crashed your browser.

So today, let’s dig into one of Meteor’s most powerful features, and hopefully clear up some misunderstandings about reactivity.

Reactive Sources

I don’t have a dog, but if I had one I would train it to bark whenever it starts feeling the need to pee. This way I could safely take it outside, and avoid ending up with a dog-urine-soaked rug.

Making a JavaScript object reactive (in other words transforming it into a reactive data source) is a bit like training your dog: you’re essentially telling the object to let you know whenever something inside it changes.

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.

Putting It Together

So if I tell you that:

  1. Session variables are reactive sources.
  2. Template helpers are reactive computations.

You can look at the following code:

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

And deduct that the counter helper will re-run whenever the count Session variable (retrieved with Session.get( 'count')) changes.

Default Reactive Sources

Now a key thing to understand is that the vast majority of variables in a Meteor app are not reactive.

In fact the list is so short that it’s worth going over it right now.

Session Variables

You can define Session variables with Session.set() and retrieve them with Session.get(), and any session variable will be both global to your whole app, and reactive.

Cursors

Cursors are what you get when you query your database with Collection.find(). They’re reactive, which is the reason why your app will change whenever the underlying data changes.

Subscription’s ready() Method

When subscribing to a publication, it can be useful to know when the subscription is done loading (in other terms, when the client has safely received the data it requested). That’s where subscription.ready() comes in, and it too is reactive.

Other Reactive Sources

The full list of reactive sources also includes Meteor.user(), Meteor.userId(), Meteor.status, as well as Meteor.loggingIn.

Default Reactive Computations

The list of reactive computations is even shorter:

Template Helpers

Template helpers (i.e. the {{variables}} you use inside templates) are themselves reactive computations. Any change to a reactive data source called from inside a helper will make the whole helper run again.

Reactive Routing

Although not strictly “default” since it’s a third-party package, it’s worth nothing that Iron Router hooks are also reactive.

As the Iron Router documentation states:

Your route functions and most hooks are run in a reactive computation. This means they will rerun automatically if a reactive data source changes.

Custom Reactivity

So is that it? Is this all this fancy reactivity stuff boils down to?

Not so fast. It turns out you can also create your own custom reactive sources and computations.

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. From the Meteor documentation:

A ReactiveVar is similar to a Session variable, with a few differences: 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().

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

I suggest reading David Burles’ introduction on this topic if you want to learn more about ReactiveVar and its big brother ReactiveDict.

Autorun: Custom Reactive Computations

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);
});

There’s actually a very detailed manual that goes into much more details about Tracker and Autorun.

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;
  }
});

Conclusion

This is just scratching the surface of what you can do with reactivity. But trust me, a strong understanding of the basics will make the rest of the journey that much easier!