Until there is a better answer, let me quote Pete Hunt, a core React developer:
The big win with Backbone models was it managed your data flow for you. When you called set()
it would inform your app that data changed. With React you'll find this to be less necessary because all you need to do is inform the component that owns the state via a callback and React ensures that all children are up-to-date. So this part of backbone is less useful IMO (and people tend to use backbone in this way with React anyway).
You don't have to pass pure JSON (though that's what I tend to do and it works well for simple data models), but you will see lots of advantages if you keep your objects immutable.
You can try this out by just calling toJSON()
on your backbone models and seeing how you like it vs passing the models around.
(emphasis mine)
Interestingly, Backbone.React.Component is the only example I found that uses toJSON
, but for some reason also uses setProps
instead of setState
(which is discouraged too).
Update
I made a simple mixin based on Pete Hunt's approach (no setProps
, no forceUpdate
):
define(function () {
'use strict';
var Backbone = require('backbone'),
_ = require('underscore');
var BackboneStateMixin = {
getInitialState: function () {
return this.getBackboneState(this.props);
},
componentDidMount: function () {
if (!_.isFunction(this.getBackboneState)) {
throw new Error('You must provide getBackboneState(props).');
}
this._bindBackboneEvents(this.props);
},
componentWillReceiveProps: function (newProps) {
this._unbindBackboneEvents();
this._bindBackboneEvents(newProps);
},
componentWillUnmount: function () {
this._unbindBackboneEvents();
},
_updateBackboneState: function () {
var state = this.getBackboneState(this.props);
this.setState(state);
},
_bindBackboneEvents: function (props) {
if (!_.isFunction(this.watchBackboneProps)) {
return;
}
if (this._backboneListener) {
throw new Error('Listener already exists.');
}
if (!props) {
throw new Error('Passed props are empty');
}
var listener = _.extend({}, Backbone.Events),
listenTo = _.partial(listener.listenTo.bind(listener), _, _, this._updateBackboneState);
this.watchBackboneProps(props, listenTo);
this._backboneListener = listener;
},
_unbindBackboneEvents: function () {
if (!_.isFunction(this.watchBackboneProps)) {
return;
}
if (!this._backboneListener) {
throw new Error('Listener does not exist.');
}
this._backboneListener.stopListening();
delete this._backboneListener;
}
};
return BackboneStateMixin;
});
It doesn't care about what kind of models or collections you have.
The convention is that Backbone models go in props
and their JSON is automatically put by mixin into state
. You need to override getBackboneState(props)
for this to work, and optionally watchBackboneProps
to tell the mixin when to call setState
with fresh values.
Usage example:
var InfoWidget = React.createClass({
mixins: [BackboneStateMixin, PopoverMixin],
propTypes: {
stampModel: React.PropTypes.instanceOf(Stamp).isRequired
},
// Override getBackboneState to tell the mixin
// HOW to transform Backbone props into JSON state
getBackboneState: function (props) {
var stampModel = props.stampModel,
primaryZineModel = stampModel.getPrimaryZine();
return {
stamp: stampModel.toJSON(),
toggleIsLiked: stampModel.toggleIsLiked.bind(stampModel),
primaryZine: primaryZineModel && primaryZineModel.toJSON()
};
},
// Optionally override watchBackboneProps to tell the mixin
// WHEN to transform Backbone props into JSON state
watchBackboneProps: function (props, listenTo) {
listenTo(props.stampModel, 'change:unauth_like_count change:is_liked');
listenTo(props.stampModel.get('zines'), 'all');
},
render: function () {
// You can use vanilla JSON values of this.state.stamp,
// this.state.toggleIsLiked and this.state.primaryZine
// or whatever you return from getBackboneState
// without worrying they may point to old values
}
}
Note: mixin requires Underscore 1.6.0+.