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?
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);
}
There's another BackboneMixin, courtesy of Eldar Djafarov, that re-renders your component when the model changes and also provides a very convenient way to get two-way databinding:
var BackboneMixin = {
/* Forces an update when the underlying Backbone model instance has
* changed. Users will have to implement getBackboneModels().
* Also requires that React is loaded with addons.
*/
__syncedModels: [],
componentDidMount: function() {
// Whenever there may be a change in the Backbone data, trigger a reconcile.
this.getBackboneModels().forEach(this.injectModel, this);
},
componentWillUnmount: function() {
// Ensure that we clean up any dangling references when the component is
// destroyed.
this.__syncedModels.forEach(function(model) {
model.off(null, model.__updater, this);
}, this);
},
injectModel: function(model){
if(!~this.__syncedModels.indexOf(model)){
var updater = this.forceUpdate.bind(this, null);
model.__updater = updater;
model.on('add change remove', updater, this);
}
},
bindTo: function(model, key){
/* Allows for two-way databinding for Backbone models.
* Use by passing it as a 'valueLink' property, e.g.:
* valueLink={this.bindTo(model, attribute)} */
return {
value: model.get(key),
requestChange: function(value){
model.set(key, value);
}.bind(this)
};
}
}
Here's his jsFiddle that demonstrates the usage:
http://jsfiddle.net/djkojb/qZf48/13/
react.backbone seems to be the most recent solution for React-Backbone integration. Haven't tested it yet though.
Related
I've recently started using ReactJS in my UI projects which has greatly simplified my UI workflow. Really enjoyable API to work with.
I've noticed recently that I've had to use a pattern in a couple of my projects that needed to aggregate data on a page. This data would live in the DOM and not be dependent on using the React state for data transitions.
This is an example implementation:
var Component = module.exports = React.createClass({
componentDidMount: function() {
this.component = new Component();
this.component.start();
},
componentWillUnmount: function(prevProps, prevState) {
if (this.component !== undefined) {
this.component.destroy();
}
},
render: function() {
return (
<div id="componentContainer"></div>
);
}
});
var Component = function(){
// Some object that dynamically loads content in a
// pre-packaged NON-react component into componentContainer
// such as a library that renders a graph, or a data loader
// that aggregates DOM elements using an infinite scroll
}
My question is whether or not this is the proper way to aggregate data into the DOM using React. I looked around for the idiomatic way of doing this, but my google-foo was unable to come up with anything.
Thanks!
EDIT - as a side note, does anyone think there will be a problem with the way I destroy the container, using the componentWillUnmount?
The main problem is that you're using an id, which is inflexible and makes assumptions about the rest of the components (because ids must be globally unique).
module.exports = React.createClass({
componentDidMount: function() {
// pass a DOM node to the constructor instead of it using an id
this.component = new Component(this.getDOMNode());
this.component.start();
},
componentWillUnmount: function() {
this.component.destroy();
},
render: function() {
return <div />;
}
});
Your componentWillUnmount was fine, but the one place you set this.component will always run before componentWillUnmount, and there's no other reason it'd be assigned/deleted, so the if statement isn't needed.
Also the arguments both weren't used, and aren't provided to componentWillUnmount. That signature belongs to componentDidUpdate.
This example is a bit contrived, but bear with me. Say I have two functions as members in a React component:
var OrderApp = React.createClass({
getInitialState: function() {
return {
selectedOrders: {},
selectedLineItems: {}
};
},
setOrderSelected: function(order, newSelectedState) {
var mutation = {};
mutation[order.id] = {$set: newSelectedState};
var newSelectedOrders = React.addons(this.state.selectedOrders, mutation);
_.each(order.lineItems, function(lineItem) {
setLineItemSelected(lineItem, newSelectedState);
});
this.setState({selectedOrders: newSelectedOrders});
},
setLineItemSelected: function(lineItem, newSelectedState) {
var mutation = {};
mutation[lineItem.id] = {$set: newSelectedState};
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
this.setState({seelctedLineItems: newSelectedLineItems});
}
});
At the completion of calling OrderApp.setOrderSelected(order, true), only the last lineItem selected state appears to have changed in my component's state. I'm guessing this is because React is batching the calls to setState, and so when I'm calling:
var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
in the second function, I'm picking up the original state each time, and so only the last mutation takes effect.
Should I be collecting all of the state mutations in the parent function and only calling setState once? This feels a bit clunky to me, and I'm wondering if I'm missing a simpler solution.
You should never be calling this.setState more than once in a given thread of execution within a component.
So you have a couple of options:
Use forceUpdate instead of setState
You don't HAVE to use this.setState - you can modify the state directly and use this.forceUpdate after you're done.
The main thing to remember here is that you should treat this.state as tainted as soon as you start modifying it directly. IE: don't use the state, or properties you know you've modified until after you've called this.forceUpdate. This isn't usually a problem.
I absolutely prefer this method when, you're dealing with nested objects within this.state. Personally I'm not a huge fan of React.addons.update because I find the syntax extremely clunky. I prefer to modify directly and forceUpdate.
Create an object that you pass around until finally using it in setState
Collect an object of all your changes, to pass to this.setState at the end of your thread of execution.
Unfortunately, this is a problem with shallow merge. You do have to collect the updates, and apply them at one time.
feels a bit clunky to me
This is a really good sign that you should be using a mixin! This code looks pretty terrible, but you can just pretend it looks pretty when you're using it.
var React = require('react/addons');
var UpdatesMixin = {
componentDidMount: function(){
this.__updates_list = [];
},
addUpdate: function(update){
this.__updates_list.push(update);
if (this.__updates_list.length === 1) {
process.nextTick(function(){
if (!this.isMounted()) {
this.__updates_list = null;
return;
}
var state = this.__updates_list.reduce(function(state, update){
return React.addons.update(state, update);
}, this.state);
this.setState(state);
this.__updates_list = [];
}.bind(this));
}
}
};
requirebin
It buffers all updates that occur synchronously, applies them to this.state, and then does a setState.
The usage looks like this (see the requirebin for a full demo).
this.addUpdate({a: {b: {$set: 'foo'}};
this.addUpdate({a: {c: {$set: 'bar'}};
There are some places it could be optimized (by merging the updates objects, for example), but you can do that later without changing your components.
I'm trying to 'reset' a ReactJS element.
In this case, the element is 90%+ of the contents of the page.
I'm using replaceState to replace the state of the element with with its initial state.
Unfortunately, sub-elements which have their own 'state' do not reset. In particular, form fields keep their contents.
Is there a way of forcing a re-render of an element, which will also cause sub-elements to re-render, as if the page had just loaded?
Adding a key to the element forces the element (and all its children) to be re-rendered when that key changes.
(I set the value of 'key' to simply the timestamp of when the initial data was sent.)
render: function() {
return (
<div key={this.state.timestamp} className="Commissioning">
...
The this.replaceState(this.getInitialState()) method doesn't actually reset children that are inputs, if that's what you're looking for. For anyone looking to just reset their form fields, there is a standard DOM reset() function that will clear all the inputs in a given element.
So with React, it'd be something like this:
this.refs.someForm.getDOMNode().reset();
Doumentation:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
If it is a form you want to reset, you simply can use this
// assuming you've given {ref: 'form'} to your form element
React.findDOMNode(this.refs.form).reset();
While I don't personally think you should store local, interim component state (like in-progress input boxes) in a centralized location (like a flux store) in most cases, here it may make sense, depending on how many you have, especially since it sounds like the inputs already have some server interaction/validation around them. Pushing that state up the component hierarchy or into some other central location may help a lot in this case.
One alternative idea off the top of my head is to use a mixin in components that might need to reset local state, and do some kind of event triggering, etc. to make it happen. For example, you could use Node's EventEmitter or a library like EventEmitter3 with a mixin like this (warning: not tested, maybe best this as pseudocode :)
var myEmitter = new EventEmitter(); // or whatever
var ResetStateMixin = {
componentWillMount: function() {
myEmitter.on("reset", this._resetState);
},
componentWillUnmount: function() {
myEmitter.off("reset", this._resetState);
},
_resetState: function() {
this.replaceState(this.getInitialState());
},
triggerReset: function() {
myEmitter.emit("reset");
}
};
Then you could use it in components like so:
React.createClass({
mixins: [ResetStateMixin],
getInitialState: function() {
return { ... };
},
onResetEverything: function() {
// Call this to reset every "resettable" component
this.triggerReset();
}
});
This is very basic and pretty heavy handed (you can only reset all components, every component calls replaceState(this.getInitialState()), etc.) but those problems could be solved by extending the mixin a bit (e.g. having multiple event emitters, allowing component-specific resetState implementations, and so forth).
It's worth noting that you do have to use controlled inputs for this to work; while you won't need to push your state all the way up the component hierarchy, you'll still want all your inputs to have value and onChange (etc.) handlers.
You could also use document.forms[0].reset()
I've got a bunch of components that can't have the same parent component, but I'd like them to share their state.
For discussions sake, lets say the top level component is called Main and the children are all called Child1, Child2, etc.
Is there any way for me to have Child1 and Child2 be children of the Main component, only in the React sense - so I can pass Main's properties and state on to them from the Main component?
From a DOM perspective, the Child1 and Child2 are located in completely different areas of my page, and actually there are about 40 of them.
Rather than directly calling setState you can use an event emitter, and all of the instances can apply the payload to their state.
We can use a mixin to abstract this behavior.
var makeChangeEventMixin = function(emitter){
return {
componentDidMount: function(){
emitter.addListener('change', this.__makeChangeEventMixin_handler_);
},
componentWillUnmount: function(){
emitter.removeListener('change', this.__makeChangeEventMixin_handler_);
},
__makeChangeEventMixin_handler_: function(payload){
this.setState(payload);
},
emitStateChange: function(payload){
emitter.emit('change', payload);
}
};
};
Then in our code we'll use EventEmitter (you can use EventEmitter2 if you're not using browserify/webpack)
var EventEmitter = require('events').EventEmitter;
var fooEvents = new EventEmitter();
var Counter = React.createClass(){
mixins: [makeChangeEventMixin(fooEvents)],
getInitialState: function(){ return {count: 0} },
render: function(){return <div onClick={this.handleClick}>{this.state.count}</div>},
handleClick: function(){
this.emitStateChange({count: this.state.count + 1});
}
};
And if you have 100 counters, they'll all update when you click any of them.
One limitation of this is that the initial state will be wrong. You could work around this by keeping track of the state in makeChangeEventMixin, merging it your self, and using replaceState in stead of setState. This'll also be more performant. If you do that, the mixin can implement a proper getInitialState.
One component of mine called ItemRenderer calls renderComponent in three places in the DOM on mount and on each update (and renders just an empty div into its real container):
https://github.com/Khan/perseus/blob/a1811f6b7a/src/item-renderer.jsx#L66-L130
This tends to be a little bit hard to reason about because the components don't go where ItemRenderer's parent tells them to, but it can work if that's what you need.
I'm running into some problems with React.js and the state not being immediately set when calling setState(). I'm not sure if there are better ways to approach this, or if it really is just a shortcoming of React. I have two state variables, one of which is based on the other. (Fiddle of original problem: http://jsfiddle.net/kb3gN/4415/ you can see in the logs that it's not set right away when you click the button)
setAlarmTime: function(time) {
this.setState({ alarmTime: time });
this.checkAlarm();
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...
When calling setAlarmTime, since this.state.alarmTime isn't updated immediately, the following call to checkAlarm sets alarmSet based on the previous value of this.state.alarmTime and is therefore incorrect.
I solved this by moving the call to checkAlarm into the callback of setState in setAlarmTime, but having to keep track of what state is actually 'correct' and try to fit everything into callbacks seems ridiculous:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
}
Is there a better way to go about this? There are a few other places in my code which I reference state I just set and now I'm unsure as to when I can actually trust the state!
Thanks
Yes, setState is asynchronous, so this.state won't be updated immediately. Here are the unit tests for batching, which might explain some of the details.
In the example above, alarmSet is data computed from the alarmTime and elapsedTime state. Generally speaking, computed data shouldn't be stored in the state of the object, instead it should be computed as-needed as part of the render method. There is a section What Shouldn’t Go in State? at the bottom of the Interactivity and Dynamic UIs docs which gives examples of things like this which shouldn't go in state, and the What Components Should Have State? section explains some of the reasons why this might be a good idea.
As Douglas stated, it's generally not a good idea to keep computed state in this.state, but instead to recompute it each time in the component's render function, since the state will have been updated by that point.
However this won't work for me, as my component actually has its own update loop which needs to check and possibly update its state at every tick. Since we cannot count on this.state to have been updated at every tick, I created a workaround that wraps React.createClass and adds it's own internal state tracking. (Requires jQuery for $.extend) (Fiddle: http://jsfiddle.net/kb3gN/4448/)
var Utils = new function() {
this.createClass = function(object) {
return React.createClass(
$.extend(
object,
{
_state: object.getInitialState ? object.getInitialState() : {},
_setState: function(newState) {
$.extend(this._state, newState);
this.setState(newState);
}
}
)
);
}
}
For any components where you need up-to-date state outside of the render function, just replace the call to React.createClass with Utils.createClass.
You'll also have to change all this.setState calls with this._setState and this.state calls with this._state.
One last consequence of doing this is that you'll lose the auto-generated displayName property in your component. This is due to the jsx transformer replacing
var anotherComponent = React.createClass({
with
var anotherComponent = React.createClass({displayName: 'anotherComponent'.
To get around this, you'll just have to manually add in the displayName property to your objects.
Hope this helps
Unsure if this was the case when question was asked, though now the second parameter of this.setState(stateChangeObject, callback) takes an optional callback function, so can do this:
setAlarmTime: function(time) {
this.setState({ alarmTime: time }, this.checkAlarm);
},
checkAlarm: function() {
this.setState({
alarmSet: this.state.alarmTime > 0 && this.state.elapsedTime < this.state.alarmTime
});
}, ...