ReactBone. Lessons from React.js applied to Backbone.js

or, how the future decodes the past.

In college I worked at the Listening Room in the basement of the college library for 3 years. It was called “work study”. It was part of my financial aid package. It was not fair. Most of my classmates were paid the same hourly wage to work in dining services which resembled work to a far greater degree than my sitting in an air conditioned room, browsing cds, tapes, and vinyl records numbering in the tens of thousands, occasionally routing one into headphones for a patron who came by to fulfill their composition class homework.

Amongst the stacks, I came across Bitches Brew by Miles Davis several times. I knew this album was supposed to be important, but it usually left me scratching my head, and turning it off in favor of some Javanese gamelan music, or maybe Metallica’s Master of Puppets.

Fast forward a couple years. Medeski Martin and Wood hit the scene.

I listened to “Friday Afternoon in the Universe” a few dozen times. And voila, the next time I listened to Bitches Brew, something clicked. Like I finally “got it” after experiencing a derivative work that presented the distilled essence of its predecessor. To make it explicit, I am opining that MMW is an evolutionary result of BB. No BB, no MMW.

Okay that’s it for the music part of this post. Sorry. Onto the code geekery.

I recently had the opportunity to head back to Backbone after spending a year or so working with React. The experience was sublime. So many subtleties of the library that I’d completely missed previously just clicked this time. I actually found myself applying the lessons of doing things “the React way” to my Backbone code.

To make the connection between my comments about music and code as subtle as a flying jackhammer (thanks for the expression, Carl!), I am saying that React is the distillation of prior art, such as Backbone, and immersing in it has helped me understand and better interact with its predecessor, Backbone, just like listening to a whole bunch of Medeski Martin and Wood helped me understand and appreciate Bitches Brew.

Here is a code snippet that demonstrates my “first ascent” style Backbone um… prowess?:

fyi: all my “old” Backbone code samples reference Goldstone Server, a fascinating project that I was hired to work on with Solinea, that used d3 to create a rich front-end dashboard for OpenStack monitoring and management. I spent two years as the only JavaScript developer on the project, owning front end development completely, and eventually becoming the top committer.

var NewPasswordView = Backbone.View.extend({

initialize: function (options) {

...

this.addHandlers();

},

addHandlers: function () {

var self = this;

$('.login-form').on('submit', function (e) {

e.preventDefault();

var $password = $('#password');

var $confirm_password = $('#confirm_password');

if ($password.val() !== $confirm_password.val()) {

goldstone.raiseWarning("Passwords don't match.");

} else {

self.submitRequest()...

}

});

}

...

});

Why this smells:

The addHandlers method works to create an event listener, but completely misses the opportunity to use the built in events hash that Backbone provides for this purpose

method works to create an event listener, but completely misses the opportunity to use the built in hash that Backbone provides for this purpose This pattern also requires the use of the much maligned self reference in order to retain invocation context when calling submitRequest

reference in order to retain invocation context when calling This creates an event listener with .on that will persist even after this view is destroyed, creating memory leaks, unless care has been taken to dispose of active listeners with .off . Glancing back at my original code for this view, I can see that that was not done8. Fairly trivial for a password change form that will hardly ever be used, but a bad pattern to get into, since on commonly loaded views, each instantiation will create a new set of listeners that will continue to add to your leaking memory problem.

How this could be improved:

var NewPasswordView = Backbone.View.extend({

initialize: function (options) {

...

// no longer needed --> this.addHandlers();

},

events: {

'submit .login-form': 'handleSubmit'

},

handleSubmit: function(e) {

e.preventDefault();

var $password = $('#password');

var $confirm_password = $('#confirm_password');

if ($password.val() !== $confirm_password.val()) {

goldstone.raiseWarning("Passwords don't match.");

} else {

this.submitRequest...

}

}

...

});

now the events hash registers a listener for the submit event targeted at the .login-form element.

element. in handleSubmit , submitRequest can be called without having to use self to reference the correct execution context.

Aside from a number of built in library methods that I had been ignoring, the more exciting growth to witness, and the main subject of this article, is the move from “monolithic views” towards smaller components that came after working with React, and returning to Backbone.

After working on Goldstone for 18 months, my last 6 months with Solinea were spent developing a client microservice for a SAAS project with React/Redux. And since then, for the last year, I’ve been working on personal projects with React, getting quite comfortable working with the library. Recently, however, I was hired to do a client project, (which I have added to my github profile at this link) for which the ideal development flow was going to be supported by going “back” to Backbone (gasp!). I don’t find very many mentions of people returning to Backbone after getting comfortable in React, but in this case, the majority of the app development consisted of displaying collections of data in various configurations to aid in the production of an annual calendar for the New York City School System. Backbone’s built in collection functions were going to be ideal for the task, and its View functionality more than adequate.

Checking through most of my Backbone Views in the Goldstone project, they are generally sprawling, encompassing functionality for multiple elements on the page. After spending some time working with React, and practicing “Thinking In React”, when returning to the Backbone, it became natural to start composing functionality from smaller individual views, taking advantage of Backbone events, and storing state in models instead of using things like $(‘.foo’).val(). In other words, practicing:

“Get your truth out of the DOM” — Jeremy Ashkenas (Creator of Backbone.js)

Here is an example (ES6 syntax):

export const EventsView = Backbone.View.extend({

initialize() {

this.collection = database;

this.filterChooser = new FilterChooser();

this.individualEventBlock = new IndividualEventBlock({

model: this.filterChooser.model,

collection: this.collection

});

this.editModal = new EditModal({

collection: this.collection

});

this.addModal = new AddModal({

collection: this.collection

});

this.render();

this.listenTo(this.collection, 'updateEditModal', this.handleUpdateEventModal);

},

onClose() {

this.filterChooser.close();

this.individualEventBlock.close();

this.editModal.close();

this.addModal.close();

},

render() {

this.$el.html('');

this.$el.append(addNewEventButton());

this.$el.append(this.filterChooser.el);

this.$el.append(this.individualEventBlock.el);

this.$el.prepend(this.editModal.el);

this.$el.prepend(this.addModal.el);

return this;

},

events: {

'click .addEvent': 'handleAddEventModal'

},

handleAddEventModal() {

this.addModal.trigger('updateAddView');

},

handleUpdateEventModal(model) {

this.editModal.trigger('updateEditView', model);

}

});

Of note:

This is the top level view that is rendered upon navigating to /events . Its main concern is instantiating additional components that will be rendered within the view, and setting up a couple listeners. The listeners are set using the events hash, or by using the listenTo syntax, which allows the listeners to be cleaned up when the view is closed by the router.

. Its main concern is instantiating additional components that will be rendered within the view, and setting up a couple listeners. The listeners are set using the hash, or by using the syntax, which allows the listeners to be cleaned up when the view is closed by the router. For those familiar with working with React, you are probably familiar with the concept of storing your state somewhere upstream, so that it is available to subcomponents lower down on the component tree. In the initialize function, I take advantage of having variable references to the subcomponents all in one place in order to share state amongst the components as appropriate. Such as: assigning the model from filterChooser to individualEventBlock. In individualEventBlock, a listener is set up so that when the filter model is changed, individualEventBlock re-renders itself.

function, I take advantage of having variable references to the subcomponents all in one place in order to share state amongst the components as appropriate. Such as: assigning the model from filterChooser to individualEventBlock. In individualEventBlock, a listener is set up so that when the filter model is changed, individualEventBlock re-renders itself. I added a close method to Backbone.View via the prototype. It will call the necessary functions to unbind listeners. [see note 1 at bottom]. If you have done some Backbone development in the past, you might recognize this as a pattern recommended by a great (if relatively old) blog post on the topic of avoiding memory leaks with Backbone views.

Backbone.View.prototype.close = function(){

this.remove();

this.unbind();

if (this.onClose){

this.onClose();

}

};

In the main routing function, when a view is replaced, the close method of the previous view is invoked.

method of the previous view is invoked. This differs from a “monolithic view”, in that each of those sub-views comprises a much smaller set of concerns than packing all of that code into one View.

Conclusion:

I don’t know how many people are left out there thinking that React is just another trendy flash in the pan, but if you are still using a MV* library such as Backbone, I recommend at least giving React try for a while until the component based design patterns click. Even if you go back to Backbone, I maintain it will make you a better developer.

This article is part of a series