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.
Related
Google Map nodes do not deal well with being removed from the DOM and everything following is trying to deal with that.
Up until now I've created a map node within a React component at the highest level in my app and never unmounted it. I'm using a bunch of convoluted css to show/hide/position this node based on what route I'm on and also using a lot of logic in the compenentWillReceiveUpdate() lifecycle method. I really want to use a Google Map within normal mounted/unmounted components and have it nested in the DOM tree where I intend it to be.
I'm playing with placing a <div id='my-google-map> node on <body> and pulling it into a component in componentDidMount() and placing it back on <body> when unmounting. Here's what I'm currently working with.
componentDidMount: function () {
const { mapWrapper } = this.refs
let mapNode = document.getElementById('my-google-map')
if (!mapNode) {
mapNode = document.createElement('div')
mapNode.setAttribute('id', 'my-google-map')
}
mapWrapper.appendChild(mapNode)
this.setState({
mapNode: mapNode
})
},
componentWillUnmount: function () {
document.body.appendChild(this.state.mapNode)
},
render: function () {
return (
<div ref='mapWrapper'>
<span>{this.props.currentDropdown}</span>
</div>
)
}
This seems to be fine and re-renders look like they just ignore the appended node but this feels weird and I'm worried I'm missing something.
Will/when will a future render delete the appended node? Should I just return false from shouldComponentUpdate() to prevent that?
Is there a better way to have a persistent non-React node within React?
are there limitations on what sort of objects that can be kept in a component's state? would be very nice to use Set(), but don't know if that's a good idea (although it appears to be legal).
var Com = new React.createClass({
getInitialState: function() {
{ set: new Set() }
},
componentDidMount: function() {
let set = this.state.set;
set.add( this.props.someAttribute );
this.setState( { set: set } );
},
...
});
the reason this feels wrong is:
it appears React attempts to protects its component state, and
if so, probably uses Object.freeze()).
if these assumptions are correct, and knowing that Object.freeze() does not freeze Set() or Map(), it might be that using them undermines React's ability to protect its component state.
which is why the question: is this a good idea or not?
I have two , mostly independent react classes
var User = React.createClass({
....
updateGameScore: function(){ this.game1.score = .... }
render: function(){ return ( <div>{this.state.totalScore}</div>);
});
var Game = React.createClass({
....
updateUserScore:function(){ how to access/modify parent here??? },
render: function(){ return ( <div>{this.state.score}</div>);
});
I need to be able to update the user totalScore when game score changes and vice versa based on some formula (irrelevant here). The components are such that game is nested in the user as a child component but cannot be vice versa. Change in user score can update game score by passing down the variable using this.state(..) , however when game score changes, what way can I use to update the correct parent User score (there can be more than one users at a time)
You can pass a handler function from User to Game via props:
var User = React.createClass({
...
handleScoreChange: function(newScore) {
this.setState({totalScore: newScore});
}
render: function () {
return (
<Game handleScoreChange={this.handleScoreChange.bind(this)} />
)
}
});
var Game = React.createClass({
...
updateUserScore: function() {
this.props.handleScoreChange(this.state.score);
}
}
In a situation like yours where two components do not share a common parent that can sensibly be used to store the change in state then you should use a store. I was unfamiliar with the concept until recently but people who know much more than me about these things recommend using Reflux as it is a streamlined/simplified version of the Flux architecture. Its simply a place to store and manipulate data independent of any one component but accessible to those components as required.
EDIT: Both Flux and Reflux seem to have been superseded by the excellent Redux.
I did another hack while waiting for the answer which also works and interesting to share for those who want to be able to call all public functions at root level.
Assuming you have Croot class
var Croot = React.createClass({
...
anyPublicFunction(val){
...
}
});
assign the result of render to a variable.
var rootComponent = React.render(
<Croot >..users & games here ...</Croot>,
document.getElementById('lobbyArea')
);
Use that variable in any child node to call any public function of Croot
rootComponent.anyPublicFunction(val);
Now I understand the concept of stores as the source of truth for a React app, but it seems that sometimes using stores is overkill, especially in UI-only situations.
For example, say I'm making an app which contains a list of movies. The app contains a search bar which lets you filter these movies according to their title. Should the value of this search bar (let's call it searchTerm) be contained in a store? Its only impact is on the list of movies shown, which is purely a UI feature. It won't be sent to the server or saved to local storage. So in my handleTextChange function, should I alert a store, or simply set the component's state:
Should it be this (using a store):
var Actions = Reflux.createActions([
"searchChanged"
]);
var Store = Reflux.createStore({
listenables: [Actions],
getInitialState: function () {
return data;
},
onSearchChanged: function (searchTerm) {
this.trigger(data.filter(function (el) {
return el.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1;
}));
}
});
var View = React.createClass({
mixins: [Reflux.connect(Store, "movies")],
handleTextChange: function (e) {
Actions.searchChanged(e.target.value);
},
render: function(){
//Render here. Somewhere there is this input element:
<input onChange={this.handleTextChange} type="text"/>
}
)};
or this (not using a store):
var Store = Reflux.createStore({
getInitialState: function () {
return data;
},
});
var View = React.createClass({
mixins: [Reflux.connect(Store, "movies")],
handleTextChange: function (e) {
this.setState({searchTerm: e.target.value});
},
render: function(){
var filtered = this.movies.filter(function (el) {
return el.name.toLowerCase().indexOf(this.state.searchTerm.toLowerCase()) != -1;
});
//Render here using the filtered variable. Somewhere there is this input element:
<input onChange={this.handleTextChange} type="text"/>
}
}
The latter example is obviously simpler. Is there a good reason to use a store to filter the data? Or should the view have a searchTerm variable and perform the filtering in the render() function?
As your examples indicate, not using a store is simpler, and arguably correct in this case.
A weak question to answer is:
Does any other component need to know about the search results?
A better question is:
Might some other component need to know about the search results?
Consider that if you add paging through results, or even a simple header of "12 results found", then those components need to know the result and will need to get it from the store. Or perhaps you'll want to add a router and have the search update the url and the url change to drive the app.
If you can say for certain that ONLY subcomponents will ever care about a value, then state is OK.
Both approaches are correct! But for your situation, filtering in component is better. Because searching result is calculable. The store should just keep the original data. The book "Developing the React edge" has an example for filterableForm, keep the search keyword in the view component is perfectly fine.
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.