I've been working with facebooks framework React.js together with Backbone for the last couple of weeks and I'm still not entirely sure what is the most appropriate way to re-render a React component when there are changes in a Backbone collection that has been passed in as a prop.
currently what I do is in componenentWillMount
I set up change/add/remove
listeners on the collection and set state when it triggers:
componentWillMount: function(){
var myCollection = this.props.myCollection;
var updateState = function(){
this.setState({myCollection: myCollection.models});
}
myCollections.on("add remove", updateState, this);
updateState();
}
render: function(){
var listItems = this.state.myCollection.map(function(item){
return <li>{item.get("someAttr")}</li>;
});
return <ul>{listItems}</ul>;
}
I have seen examples where the models are cloned to the state:
var updateState = function () {
this.setState({ myCollection: _.clone(this.myCollection.models) });
};
I've also seen variants where model/collection in props is used directly in render instead of using state, and then forceUpdate is called when the collections/model changes, causing the component to re-render
componentWillMount: function(){
var myCollection = this.props.myCollection;
myCollections.on("add remove", this.forceUpdate, this);
}
render: function(){
var listItems = this.props.myCollection.map(function(item){
return <li>{item.get("someAttr")}</li>;
});
return <ul>{listItems}</ul>;
}
what benefits and drawbacks are there to the different approaches? Is there a way of doing it that is The React way?
Advertisements. Collections are ordered sets of Models. We just need to extend the backbone's collection class to create our own collection. Any event that is triggered on a model in a collection will also be triggered on the collection directly.
There are basically two ways in which the data gets handled in React. It gets handled through state and props. In another way, it can be said that two types of Model in React are there to manage the data for the components. Both state and props are plain JavaScript objects.
They serve the same purpose as JavaScript functions, but work in isolation and return HTML. Components come in two types, Class components and Function components, in this tutorial we will concentrate on Function components. In older React code bases, you may find Class components primarily used.
Instead of manually binding event listeners, you can use a mixin based on this BackboneMixin to help automatically bind and unbind the listeners:
https://github.com/facebook/react/blob/1be9a9e/examples/todomvc-backbone/js/app.js#L148-L171
Then you simply write
var List = React.createClass({ mixins: [BackboneMixin], getBackboneModels: function() { return [this.props.myCollection]; }, render: function(){ var listItems = this.props.myCollection.map(function(item){ return <li>{item.get("someAttr")}</li>; }); return <ul>{listItems}</ul>; } });
and the component will be rerendered when anything changes in the collection. You only need to put BackboneMixin on the top-level component -- any descendants will be rerendered automatically at the same time.
IMO, React is still very new and there are very few established rules on how to work with data and reactive models like Backbone. This is also a strength, if you have an existing application - react can be integrated on some smaller parts of it without redefining the entire data flow.
I believe that since React can call render "smart" at any time – that is only re-rendering parts that have changed – you don’t really need to pass data as states. Just pass the data, add listeners on the top component and call forceUpdate
when the model has changed and it will propagate down nicely.
It just seems more "right" to pass backbone models as props, not states.
One important thing that I learned the hard way is to use the model.cid
as key (and not Math.random()
) when rendering backbone model lists:
var listItems = this.props.myCollection.map(function(item){
return <li key={item.cid}>{item.get("someAttr")}</li>;
});
Because otherwise React won’t be able to recognize what model to re-render because all of them will have new keys on each render.
I had been playing around with the BackboneMixin mentioned here and a couple other react resources (of the limited info currently out there). I found that when I was listening to a collection that was being updated from the server, just as many n 'add' events are going to be triggered on the collection and listened to by the BackboneMixin, thus calling force update n number of times, which calls render and whatever is called from render n number of times.
Instead, I used underscore/lo-dash's throttle method to limit the number of times forceUpdate would be called. At the very least this has limited the render method from being called so much. I know react isn't actually doing any DOM manipulation there, and its just a virtual DOM, but still there is no reason it should be called 100 times for 100 immediate additions to a Collection.
So my solution looks like https://gist.github.com/ssorallen/7883081 but with the componentDidMount method like this instead:
componentDidMount: function() {
//forceUpdate will be called at most once every second
this._boundForceUpdate = _.throttle(this.forceUpdate.bind(this, null), 1000);
this.getBackboneObject().on("all", this._boundForceUpdate, this);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With