

Ractive.js - dave1010uk
http://www.ractivejs.org/

======
andrewvc
Out of curiosity, where's the love for knockout? It's mature, and works well,
but gets no press anymore.

~~~
curveship
I've been using knockout heavily for a project recently and have come to
really like it. This is a small to mid size website, about 6 engineer months
of effort and somewhere in the mid 10klocs of custom javascript. Some
strengths of knockout:

\- it builds on the "Good Parts" of javascript, in that KO observables are
just closures. You can pass them around or attach them to different objects
however you feel like. For instance, we built a standard UI widget for
entering currency values. Anywhere we need to accept currency, we instantiate
one of these widgets and pass it the observable that holds the value. Clean,
easy, encapsulated.

\- Some people have a distaste for the "()" syntax: obj.prop() rather than
obj.prop. There's some truth there, but the "()" syntax has one big benefit
that overrides the slight ugliness in my experience: it catches naming errors
immediately right at their source. If you've ever had to refactor a fairly big
piece of JS, you know that one difficulty is correcting all the changed
references. And if you miss one, the bug may be subtle, as the old reference
doesn't except but quietly returns undefined. With knockout, the old reference
blows up immediately, as undefined isn't a function. This is a big win.

We had some growing pains with knockout. Lessons learned:

\- don't build anything but a small web app the way the knockout examples
demonstrate. The reason is that while knockout claims to be MVVM, their
examples are really just VVM -- there're no distinct model classes. They store
data directly in the viewmodel classes. Bad things follow as your app grows:
since the data is in the VM, any view that needs the data (i.e., all of them)
needs to be bound to that same VM, meaning that your one VM ends up needing to
feed several views. You find yourself annotating your viewmodel into sections
-- "// these properties are used by view X ... // these properties are used by
view Y" etc. Yech. Much better to have true model classes, then build a VM
class on top of the model for each of your views.

\- we now make heavy use of named templates to decompose large views into more
manageable pieces. We wrote a simple custom binding to make this easy: since
each VM is designed to work with a single view (see prior note), we put the
#id of the view's named template into a .Template property of the VM class.
The custom binding inspects this property and binds the VM class to its named
template.

~~~
aforty
Oh how I wish you had written that 6 mons ago. Learned those same lessons in a
painfully slow way.

I've been using knockout for the last 3 years, mostly for small projects but
the last one has been a big one. Growing pains but its working out great.

Knockout needs more love.

------
skore
Very interesting, but one thing Re: AngularJS

> dependency injection minification headaches

Meh, that's hand-wavy at best. The only thing you have to do to pass
minification is to include the injected dependencies in an array like so:

    
    
        ['$stateProvider', '$urlRouterProvider',
        function ($stateProvider, $urlRouterProvider) {
            // Stuff
        }]
    

You get used to it in seconds and then it's not really an issue anymore.

~~~
tbranyen
I'm currently using this style in a RequireJS/Angular project. Everything
works great, but it's pretty ugly. We should strive to not write ugly shit,
but good clean expressive code. This is so non-standard it hurts. There are
plenty of other ways the Angular team could have gone; CommonJS or AMD would
have been better choices IMO.

~~~
likeclockwork
Use ngmin, it converts into the ugly style.
[https://github.com/btford/ngmin](https://github.com/btford/ngmin)

There's also a grunt plugin. [https://github.com/btford/grunt-
ngmin](https://github.com/btford/grunt-ngmin)

~~~
tbranyen
ngmin doesn't work with r.js. My response was about using it with AMD modules.
I know there were a few efforts to get ngmin/r.js working together, but last
time I searched around I didn't see anything promising.

~~~
likeclockwork
I wonder how much work it would be to actually integrate DI/module system and
RequireJS.

------
codereflection
Would be nice to see this up on [http://todomvc.com/](http://todomvc.com/)

~~~
robotmay
I actually find that website almost entirely useless. A to-do list means
nothing and demonstrates nothing useful. It has no associations between models
and nothing particularly complex. I have tried so many of these frameworks and
found crippling flaws in all of them, but on the surface they all look peachy
when compared via a to-do app.

It's pretty much only useful if you want to write a to-do app.

~~~
beefsack
I completely disagree. I find it very useful to see small, easy to digest, and
easy to compare example apps when new frameworks come out for a first
impression to help decide if I want to have a play with it myself.

------
nathansobo
The examples for these kinds of frameworks always show you data binding for
scalar values. That's easy. What's the story for more complex updates?

~~~
seanmcdirmid
Declarative data binding is intrinsically limited. If you want something that
supports more complex updates, you need to start looking into things like
incremental algorithms, self-adjusting computations, damage and repair, logged
reversible effects, iterative processing methods, and so on.

Say you want to write a compiler whose input is "bound" to a source file; when
the input changes, you don't want to recompile the entire file all over again.
Instead, you memoize at AST node boundaries, trace dependencies between AST
nodes, and then "clean" nodes when they become dirty, detecting changes that
affect other nodes to clean them also.

To be honest, I find these data binding frameworks to be very boring, since
we've been doing this forever (I did my dissertation on one myself). We really
should be looking at the next level that supports incremental processing of
more complicated updates.

~~~
curveship
Knockout does incremental updates as well as memoization. It instruments the
evaluation of computed observables to determine the dependency relationships.
I haven't delved into Ractive.js closely, but it sounds like it does something
similar. How is what they do different from what you're describing?

~~~
seanmcdirmid
Re-evaluation is easy enough when your computation is pure. But once you throw
in some imperative effects, like adding a name binding to a symbol table, all
of the declarative data binding frameworks fall down very quickly.

There is a lot of research in this area, concurrent revisions at MSR or self-
adjusting computations at CMU to name a couple. Also, if you've seen Bret
Victor's videos and wonder how any of those could be implemented for real,
then this is where we need to go. It will be a fun trip though.

~~~
curveship
You're awesome. I've been playing around with a dependency network in JS
recently, and I'd just been puzzling over exactly that question: how best to
handle the case where a computation has side-effects. So I'm now busy googling
concurrent revisions and self-adjusting computations. Got any further
references to throw my way? Is there a standard literature on this stuff?

~~~
seanmcdirmid
Actually it's a problem I'm working on right now, but I have nothing to share
yet. Some sort of restricted programming model is needed, in my current system
effects must be reversible and commutative, the last one being a major
restriction but workable for most programs (e.g. set add is typically
commutative, list add is not but can be hacked, dictionary set to the same key
is not but can restricted by set once semantics).

------
sambeau
This is fascinating: I've been writing a very (and I mean _very_ ) similar
library that also uses the concept of a Parallel DOM (dependents, views
updating on a 'data' property, nodes named by standard ids, all child nodes
with IDs scanned for and bound to a property), I had just decided to create
the concept of 'Keypaths' as my events were clashing on ids.

The thing I have that I can't see here is a selecting/switching node:
essentially a page or a tab that holds multiple states but only presents one
at a time.

Thus makes me very happy and very sad: happy as I don't have to write all
things things now; sad becasue I don't get to write all these things now.

~~~
rich_harris
I would feel the same way if we switched places! Really interesting that we
have such similar approaches.

------
jonahx
Hi Rich,

This looks promising. Can you comment on similarities/differences between
Ractive and facebook's React.js in terms of style and performance?

Also, looks like the github minified version is 72k, is that correct?

~~~
rich_harris
Thanks. Performance-wise, I haven't tested Ractive and React against each
other, but that would be a test worth doing. Ractive is definitely built with
performance in mind - it uses dependency tracking to ensure that the DOM is
only updated when necessary.

As far as the style goes, I'm obviously biased, and I'd encourage people to
try both to see what suits them best. For me, mustache syntax is very
readable, even by non-devs (which makes collaboration and rapid prototyping
very easy), and the Ractive API aims to be as easy as possible.

I find React's JSX to be a bit of a barrier (I'm aware that it's not essential
to use JSX with React), but as I say it's a matter of personal preference.

Yes, it's currently about that size (27k gzipped) - one of my goals for future
versions is to try and wrangle that down. It's not that big a library for the
amount of functionality it contains, but smaller is always better.

~~~
peterhunt
Hey there! I'm on the FB/IG React core team.

Syntax differences aside, seems to me that React is geared more towards
interactive UI components and Ractive is oriented more towards reactive
documents, kind of like Tangle[1]. Is this accurate?

The similarity seems to be in the philosophy of the implementation
(manipulating a fast representation and rendering to the DOM). It's pretty
cool to see that we both discovered the "secret" though :) Seems like we're
validating each others' projects!

If you ever want to nerd out we hang out in #reactjs on freenode.

[1] [http://worrydream.com/Tangle/](http://worrydream.com/Tangle/)

~~~
mateusmaso
I agree with React being geared more towards interactive UI components, but on
the other hand you could use Ractive with WebComponents/Polymer instead of MDV
to get all this powerful component lifecycle.

Still working on a solution that mix Backbone for core models + Polymer for
view lifecycle and custom elements + Ractive for awesomeness updating.

The only concern I have is that some people do not like to use logicless.. I
think React is powerful for letting you declare full javascript evaluations
and expressions on your template code and giving you more flexible/productive
way of writing.

~~~
rich_harris
Ractive does have a way of including JavaScript evaluations in the template
which as far as I'm aware is unique:
[http://learn.ractivejs.org/#!/expressions/1](http://learn.ractivejs.org/#!/expressions/1)

The JavaScript expression is parsed into an AST, and references to parts of
the data model are extracted so they can be dependency tracked and evaluated
against the right context. Quite hard to explain concisely but I think it does
what you're after.

------
smagch
Its naming is a bit conflicting to component/reactive. The name "reactive" is
so presumable that other people will choose in the future.
[https://github.com/component/reactive](https://github.com/component/reactive)

I haven't looked into Rivet.js, component/reactive or other equivalent
library. I'm just planning to research which library is the best fit for me. I
don't really like full-stack framework like Angular. It was nice to know yet
another candidate.

Does anyone know any other reactive template engine?

~~~
tlrobinson
The name is actually "Ractive"

I like Knockout.js

~~~
smagch
Ah, "Ractive". Shame on my eye.

~~~
tlrobinson
It wasn't your eye, the title was wrong for awhile!

------
rubiquity
So it's data binding + templating? Why would I use this over something like
Rivets.js?

~~~
rich_harris
Data-binding is the most obvious feature of Ractive but there are others -
easier event handling, transitions, animations, JavaScript-ish expressions
within templates, server-side parsing and HTML rendering, and so on.

On the other hand, Rivets is much smaller, so if you only want the data-
binding then it's a fine choice. Personally I really enjoy the freedom of
writing mustaches straight into my template, rather than using data-bind
attributes everywhere, but it's horses for courses.

~~~
GMFlash
Rivets.js can do mustache-style templating too:
[https://github.com/mikeric/rivets/pull/182](https://github.com/mikeric/rivets/pull/182)

------
boothead
Nice - looks very simple and lightweight! I especially like the effort put
into the interactive tutorial documentation that these frameworks have.
Angular and knockout both had stand out docs too!

Is there a particular niche for this framework would you say e.g. small, fast
to market apps like news stories - or is is suitable for larger projects where
you might normally reach for more of a "framework"?

~~~
rich_harris
Library author here - thanks! The interactive tutorials borrow very heavily
from Knockout (in fact Knockout was a big source of inspiration all round).

It was initially designed to scratch my own itch - I'm a newsroom developer at
the Guardian, where we turn around projects with fairly tight deadlines, so I
guess you could say it's optimised for that. (Blog post here:
[http://www.guardian.co.uk/info/developer-
blog/2013/jul/24/ra...](http://www.guardian.co.uk/info/developer-
blog/2013/jul/24/ractive-js-next-generation-dom-manipulation)) In particular I
wanted an API that wouldn't be completely mysterious to journalists who are
starting to dabble with code.

There's actually a separate discussion on Ractive at
[https://news.ycombinator.com/item?id=6096545](https://news.ycombinator.com/item?id=6096545)
\- I'm not sure what the HN etiquette is, can anyone enlighten me? (I
appreciate both submissions though!)

~~~
boothead
I've had a look at the last slide now - I think the transitions could use a
little work. With that in mind - how hard would it be to integrate with d3 do
you think?

~~~
rich_harris
Yes, that slide is still a bit of a work in progress.

You can certainly use D3 (or any other library) alongside Ractive, though
things could go awry if two libraries were trying to manipulate the same bit
of the page at the same time - e.g. if an element has a style attribute like
style='left: {{left}}px;' and D3 modifies the element's style, D3's changes
will only last until the value of {{left}} is updated.

~~~
ganarajpr
So what is ractive.js's equivalent of $scope.$apply ? $scope.$apply is used
for this specific purpose, to notify angular of any external changes.

~~~
rich_harris
$scope.$apply is necessary because sometimes changes to the model happen
outside Angular's event loop.

Ractive doesn't have this issue because updates always happen explicitly with
ractive.set( 'key', value ) (or with array mutator methods). It's not quite as
magical as Angular's frictionless updates, but it gives you slightly more
control (e.g. you can do ractive.animate( 'key', value ) and so on), without
needing to inherit from custom observable classes.

------
xrd
Can someone comment on where to find performance information on this
framework? AngularJS has some very smart ideas that allow it to be magical and
performant. This looks very cool, and I would like to make sure it is not just
polling for changes. I'm suspicious because there is no $apply required. :)

~~~
rich_harris
Definitely no polling! Model changes are done explicitly with ractive.set()
(or ractive.animate(), or array mutator methods). It uses a dependency
tracking mechanism to update the view with the fewest calculations and DOM
touches necessary.

------
mannix
I have a question, since this has been bugging me with other js MV* frameworks
lately... Is the set() method necessary for updating single attribute? All
browsers have supported getter/setter APIs (__defineGetter__ and
Object.defineProperty), why not take advantage of them? Being able to say this
would be cool:

    
    
        div.color = 'blue';
    

instead of:

    
    
        div.set({ color: 'blue' }); or div.set('color', 'blue');
    

Or would this cause other problems that I'm not thinking of?

Edit: my ruby cap is on a little tight today. Javascript programmers might not
expect extra logic to be run just by updating a property. I still think it
would be cool though :)

~~~
mnemonik
Many of these frameworks want to support IE 8, which is the default browser of
windows 7. IE 8 only supports Object.defineProperty with DOM objects[0], which
can be infuriating.

No one should use __defineGetter__ and __defineSetter__ because they are non-
standard and deprecated[1].

[0] [http://kangax.github.io/es5-compat-table/#define-property-
ie...](http://kangax.github.io/es5-compat-table/#define-property-ie-note)

[1] [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineGetter)

Edit: left this tab open and I see this point has since been made, but I will
leave the comment anyways.

~~~
mannix
Yes, the only reason I'd use __defineGetter__ would be a fallback for
Object#defineProperty.

I'm curious if the IE8 limitation could be worked around, using something
like:

    
    
        if (ie8)
            RactiveBaseModel.prototype = HTMLAnchorElement.prototype;

------
Dekku
Doesn't seem to work in Firefox: "TypeError: templateBlock.innerText is
undefined"

~~~
rich_harris
That's very odd - which version of FF are you using? From the error I'm
guessing you saw this on the tutorial pages?

~~~
ineedtosleep
Same for me on the homepage, messages.unread setter:

Firefox Aurora 24.0a2 (2013-07-19) and 2013-07-20

TypeError: templateBlock.innerText is undefined @
[http://www.ractivejs.org/:217](http://www.ractivejs.org/:217)

~~~
rich_harris
Well, that's embarrassing. Not sure how I missed that! Fixed now, as soon as
GitHub pages updates. Thanks for the heads up

------
curveship
I like a lot of this, but one part I'm not so sure of is that, if I follow the
docs, all get/set operations need to be from the root of the model. They take
a "keypath" that starts at root, like "obj.get('subObj1.subArray[3].prop')".
It seems to me like that would make it difficult to decompose a large
application into component pieces. Your components, to do any get/set
operations, would need to know their path from root. And what if that path
changes, like an array that's modified? Seems like it could become a
considerable barrier in mid-to-large applications.

~~~
rich_harris
You're right, all keypaths start from the root. One approach for large apps is
to split the UI up into several Ractive instances (which might all be bound to
a single global model, but 'start' at different keypaths).

Array modifications aren't generally a problem, because you'd reference the
index from within the template - Ractive keeps the references up to date.
Also, proxy event handlers are aware of the current keypath context - see this
JSFiddle to see what I mean
[http://jsfiddle.net/KJt2Y/2/](http://jsfiddle.net/KJt2Y/2/)

------
scottmessinger
I like it's elegance, but it looks seems similar to using an Ember.js view and
Handlebars templates.
[http://j.mp/reactiveinember](http://j.mp/reactiveinember)

~~~
rich_harris
Thanks, this is an interesting example. I'm not wild about the extra DOM it
creates (the placeholder <script> tags, etc) but I'm intrigued about what's
going on under the hood - will poke around the source code.

------
apidoc
Nice little library. I think i will have a close look and implement it into
apiDoc ([http://apidocjs.com](http://apidocjs.com)) Templates - for now i use
crude jQuery events (quick & dirty coded). With Ractive i think i can make the
code cleaner and easier to modify.

At the moment i prefer Backbone in my other projects, but it is to "heavy" for
a small single page (especially for users that did not know backbone and want
to modify the templates).

------
boubiyeah
Cool website and examples!

You should probably explain how ractive.js differentiates itself from
knockout. It's the direct competitor out there, not angularJS which is doing
things very differently.

I glanced at the source and (correct me if I'm wrong) it seems array
templating is done by simply comparing the length of the current and previous
arrays? That's naive and buggy?

Good luck

~~~
rich_harris
Thanks! Yes, a lot of the feedback I've had through various channels has
involved 'how does this compare to x', and I'm pleased to see that a lot of
people have picked up on the similarities to Knockout. On the project homepage
I specifically refer to Angular because my sense is that it will be more
familiar to more people, and despite the many architectural differences
they're attacking the same problem space (though Angular deals with
xhr/routing etc whereas Ractive is focused on DOM stuff).

I'll add a comparison table of some sort to my (lengthening!) todo list.

Could you elaborate at all on 'naive and buggy'? I think I probably know what
you're getting at, but I'd be interested to hear your thoughts.

Array sections do have a concept of 'smart updates' \- if you do e.g.
list.push( newItem ) or list.splice( index, 1 ) then it will take a more
surgical approach to updates. You can see this in action at
[http://www.ractivejs.org/examples/todos/](http://www.ractivejs.org/examples/todos/),
explanation of array modification at [https://github.com/Rich-
Harris/Ractive/wiki/Array-modificati...](https://github.com/Rich-
Harris/Ractive/wiki/Array-modification)

~~~
boubiyeah
I must have missed the 'smart update' bit :)

------
Narretz
Sounds like a cool alternative to angularjs when you can't use or don't need a
full-blown framework with routing, http layer etc.

~~~
nkerkin
Just to clarify, you don't _need_ to use routing/http/etc to get the benefits
of Angulars DOM binding.

------
modarts
Can anyone comment on using Ractive as a replacement to Backbone.View? I'd
think that making Ractive the view layer in a Backbone app would go a long way
in augmenting a lot of the things that Backbone refuses to do in views
(binding, smart DOM management/manipulation, actual templating support)

~~~
rich_harris
There's a Ractive.extend() function for this exact use case, which allows you
to incorporate all the functionality your Backbone View would have in a
reusable constructor. Info on the wiki: [https://github.com/Rich-
Harris/Ractive/wiki/Ractive.extend()](https://github.com/Rich-
Harris/Ractive/wiki/Ractive.extend\(\))

There's also a concept of 'adaptors' which are ideal for Backbone.Model <->
Ractive binding, though it's a bit experimental and incomplete at the moment.
You can bind to a model manually like

model = new MyBackboneModel( attrs ); ractive = new Ractive({ el: whatever,
template: tpl, data: model.attributes });

model.on( 'change', function ( model ) { ractive.set( model.changed ); });

------
d0m
I _really_ like this library.. it's been a while I've been looking for
something similar. Angular was a bit "too big" for some of my projects, and
backbone was just too verbose. Something like this will let me keep the code
clean while still not getting involved in a big framework.

------
usethis
@rich_harris:

1\. Great library, thanks for contributing!

2\. What was your motivation to add transitions and animations, isn't that a
bit out of scope?

3\. I has no dependencies, why did you choose not to?

~~~
rich_harris
1\. Thanks!

2\. Not at all - animation is something I need in my day job all the time, and
having .animate() saves me so much time. I wasn't sure if I'd get much use out
of transitions (I was jealous of ng-animate and wanted to see if I could
implement something similar!) but I've found them very useful. Transitions
probably need a bit more work though.

3\. It just doesn't need any. The amount of code I could save by using a
helper library like Underscore wouldn't be worth the potential extra hassle of
version headaches etc. Personally I much prefer using 'fire and forget'
libraries with no dependencies.

------
leke
So as this is DOM manipulation, it's a competitor to jQuery, right? If so,
what are the advantages over jQuery, and would it make jQuery redundant now
(for new projects)?

~~~
rich_harris
I wouldn't say it makes jQuery redundant - the AJAX stuff is helpful (if
replaceable) and it does a great job of e.g. $div.height() and so on, which
can be a source of cross-browser headaches without it. Though personally yes,
I find I rarely use jQuery now I've got Ractive.

The difference is the difference between declarative and imperative
programming. With jQuery you have to describe the steps that the browser has
to follow in order to do something (e.g. $button.toggleClass( 'selected' ),
whereas with Ractive you're much closer to simply declaring your intentions
(e.g. ractive.toggle( 'selected' ), assuming your template references that
variable). It's an inherently more scalable approach (which isn't to knock
jQuery at all - they're attacking different if overlapping problems).

------
CoryG89
Is there anyway to use any other templating system with Ractive other than the
Mustache style syntax? Couldn't use microtemplates / EJS style templating for
example?

~~~
rich_harris
No, Mustache only I'm afraid. The reason is that it's not actually using
Mustache or any of the Mustache-based engines (such as Hogan.js) - it's using
its own implementation.

That's because almost all other templating engines are string -> string (i.e.
you add data to a string template, and get HTML) whereas Ractive is string ->
DOM. Microtemplates, EJS etc would have to be reimplemented as string -> DOM
engines separately if they were to work with Ractive.

~~~
CoryG89
Seems like that should be decoupled for this exact reason.

~~~
rich_harris
Maybe one day ;-)

------
ghostdiver
that is false statement actually

 _HTML is an amazing language for creating static documents, but it was never
designed for interactive web apps_

------
oakaz
how would you compare it to github.com/component/reactive ?

------
alphonse23
another frontend framework for Dom manipulation, so would this be the 50th one
out there on the web?

~~~
invalidOrTaken
Yes, and you better hope 50 more come out. Early adopters do your shit work
for you. "______.js is picking up speed" is code for "Thousands of hours of
unpaid labor have gone into making better tools for you, and evaluating them
against other tools."

------
SkyMarshal
It's "Ractive.js", not "Reactive.js".

~~~
rich_harris
Indeed - as in 'interactive'. Got the name from Neal Stephenson's The Diamond
Age, in case anyone wondered...

Though it is also reactive, in the 'reactive programming' sense.

~~~
SkyMarshal
Edit: Question answered in the other thread:

[https://news.ycombinator.com/item?id=6096660](https://news.ycombinator.com/item?id=6096660)

I haven't dug into it yet, but from the description it reminds me a bit of
Facebook/Instagram's React js framework [1].

React creates a lightweight model of the DOM in memory, and when any part is
changed, it sends a diff to update the browser DOM. No bindings necessary:

 _" When your component is first initialized, the render method is called,
generating a lightweight representation of your view. From that
representation, a string of markup is produced, and injected into the
document. When your data changes, the render method is called again. In order
to perform updates as efficiently as possible, we diff the return value from
the previous call to render with the new one, and generate a minimal set of
changes to be applied to the DOM."_

Your description is similar, but it's not clear how exactly the update is
made:

 _" In this example, Ractive.js constructs a parallel DOM representation which
is aware of its dependencies on the values of user and messages.unread. When
those values change, it knows exactly which parts of the real DOM need to be
updated.

This kind of surgical DOM manipulation is more efficient than constantly
trashing views only to re-render them, and it scales far more elegantly than
manually updating elements."_

How does the DOM update mechanism work?

[1]: [http://facebook.github.io/react/blog/2013/06/05/why-
react.ht...](http://facebook.github.io/react/blog/2013/06/05/why-react.html)

~~~
rich_harris
Basically, items in the parallel DOM register themselves as dependants of
'keypaths', so a {{user.name}} mustache depends on the value of user.name. If
you do ractive.set( 'user', { name: 'Bob' }), Ractive scans the dependency
graph to find items that may need updating. The mustache will compare its new
value to its old value, and if it has changed it will update the text node
that it is mirroring.

I'm not intimately familiar with how React updates happen so I can't comment
on how similarly we're doing things, but it looks like we have the same kind
of approach.

