Transcript
  • Meteor
  • Tutorial

UI Reactivity in Slow Motion

In the last couple of episodes, we looked pretty deeply at Spark and the Meteor rendering process. In this episode, I'd like to take a step back and just simply visualize user interface reactivity in slow motion. To do that, I'm going to use two templates. I have a list template and a listItem template. The list template is just going to iterate over a list of items and print or render the listItem template for each one. And the listItem template just shows the title of the item.

And so over in the browser, I can see that I have two items currently. And then down in the console, I print this rendered list to the console when the list template is finished rendering. Over in my JavaScript, you can see down in the list.helpers object I have the items helper defined there. And for now, it just returns an array called items, which is defined up here.

And so this is a non-reactive data source. That means it's just a regular array, and it doesn't invalidate any contexts if the data inside of it changes. So what does that mean? Well, if I jump over to the JavaScript console, I can take a look at the items array that was used to render the list above. And if I change one of those items, like if I say items 0 dot title is equal to updated, the object gets updated. But the user interface doesn't change. And that's because this array has no way of telling Meteor to re-render the template in which the array was used.

So let's go back to the JavaScript code and try something a little different. So instead of working with a raw array, let's work with this document collection that I created up here. I created that document collection by just setting it equal to a new Meteor collection and passing null to say that it only should be on the client, and that it's an unnamed collection. So we won't be using this anymore. And then here I have a C function that just puts three documents into the array when Meteor starts up on the client.

So let's work with that document collection. So I'm going to get rid of return items. And instead, I'm actually going to return a different array. But this time it's going to be the result of calling DocumentCollection.find. And then I'm going to call fetch just to get the array of all of those items. And then we'll just return it.

So let's take a closer look at what we've done. We've called-- let me get my pen-- the method find on document collection. And this returns a cursor. And the cursor is sort of magically reactive. That means that if the data inside that cursor changes, it will be able to tell this template to re-render itself.

And so over in the console, what you're seeing is we actually have two rendered lists get printed to the console. So you see this rendered callback here that gets called every time the list is rendered? Well, it's rendered the first time when this collection starts off as empty. But then as soon as the document collection is seeded, the list gets rendered again. Because that causes this cursor to invalidate this context.

So that just means that the data inside this cursor changed inside this. As a result of calling this find method, we get a cursor. And the data inside that cursor changed. And when that happens, re-render and put all of the documents on the screen.

Let's look at that in the browser in slow motion. To do that, I've set a few breakpoints inside of Spark. And I'm going to enable the breakpoints here and refresh the browser. So you can see that I've pause before the DOM nodes have been put on the screen. And what you can see is down at the bottom the list has already been rendered.

And if I press Play, you don't see it on the screen. Because there's no items yet. Because the document collection has not been populated. But if I press Play, now you see the documents on the screen.

But we're dealing with a reactive data source now. The DocumentCollection.find creates a reactive context inside this items function, which causes the template to re-render if we add or change items in the document collection. But what exactly is being re-rendered here? Is it the individual list items, or is it the entire list?

So to find out, let's update one of the documents. I'll call DocumentCollection.findOne just to get the top item. And then what I'll do is I'll say, DocumentCollection.update. And we'll update that document. And we'll set the title to something new, like updated.

OK, so I've paused it. Now, the DOM hasn't changed yet. But you can see because the document has been updated, the cursor is reactive and fires its update, or invalidates the context. And so you can see that the template's going to be re-rendered again.

So if I press Play, the first step is the entire list has been taken off the DOM and off the screen. And so we don't see it anymore. And if I press Play again, you see that the entire list is put back onto the screen. And the first item has been updated.

So this works fine usually if the list is small enough. Because the re-rendering happened so quickly that you don't even notice. You have to put in a breakpoint into the debugger and really slow things down to notice that the entire list is being taken off the DOM and put back onto it again with an updated value.

But what if we just want to update an individual item as it changes, not the entire list? So we really want the reactivity to happen at the individual list item level. So to do this, let's make a couple of changes.

First, I'm just going to add a rendered callback to the listItem template itself so that we can see when it's been rendered. And then what we want to do here is instead of returning an array from the items helper of the parent template, we want to return a cursor.

So if I call the find method, I'll return a cursor. And now what's happening over in the HTML is the each method is now iterating not over an array, but over a cursor. And that's going to do something special in Meteor. It's going to create a reactive context around each item. And it's going to work hand in hand with the cursor so that if an individual item changes in the cursor, only the item that has been changed will be updated in the user interface. And so let's walk through that in slow motion.

OK, so I'll re-enable my breakpoints and refresh the browser. OK, so you can see that the list is initially rendered. And all we have right now is an empty, unordered list. And if I press Play once, you can see the first document is added, and then the second, and then the third. And then all of my callbacks fire. So this looks a bit different than before where the entire list renders at once.

So let's change one of the items in the list. We'll do the same exact thing we did last time. I'll call findOne just to get the top level item. And then I'll update it by calling DocumentCollection.update, passing the document as the selector. And we'll set the title to updated.

OK, so I'll press Play again. And you can see that now only that Document 0 has been removed from the screen, has been taken off the screen. And then if I press Play again, a couple more times actually, the updated document is put back onto the screen. And so now instead of taking the entire list off the screen and putting it back on, Spark and Meteor are just taking that one item off the page and updating that.

So I think this is a pretty powerful system. But it can be a little confusing to understand at first. The two key takeaways from this video are that if a reactive data source invalidates its context, the template in which it was used is the one that ends up being re-rendered. But if we use or return a cursor in combination with the each handlebars helper, Meteor is smart enough to only update the individual list items that have changed. And hopefully visualizing this in slow motion in the debugger helped you to see what's happening.