I have been discussing a lot lately with smart guys about what is the best way of organizing your application using React.js and I would like to share what I have learnt.
Facebook released react two years ago saying "Hey guys, here you have the V for your MVC web apps!" and it turned out to be a great V. But how should I organize the rest of the app? People tried to mix react views with backbone models then to try to fill the gap then, but those solutions were a bit overkilling.
Last year React guys appeared again to say "Hey guys, use Flux, your data should flow in just one direction". The second commandament was really good too. Receiving the props from above and reducing component states reduce the complexity of developing web apps. But it made grow more questions in developers' heads: where should I put the code to fetch data from the server? How can I handle async actions? How can a child component communicate with its parent?
The rise of Flux libraries Whenever questions are made, people try to answer their own way, and a lot of custom flux implementations started to appear: Fluxxor, Reflux, Fluxible, Marty... Even I released my own implementation: Fluxify.
All of them do the same and all of them are incompatible with the others. Once you start using one implementation, your react components get highly coupled to that library, and it is not easy to switch from one to another.
But the way that React.js components are defined encourages to reuse and encapsulate them. Why can't we use them like if they were web components?
Events to the rescue Flux idea is great, flow like water my friend. But the way that Facebook propose to flow is pushing the water with the hand. Dispatching actions from the components is too imperative, our views need to say "Hey app, something happened" instead of "Hey app, do something".
Events are the natural way of saying that something has happened in web pages. They have been used succesfully by HTML elements for years and it is the recommended way for the a web component to communicate with the world outside its shadow DOM. And the best part of events is that they are shipped by every browser, so why not to use them with react.js?
Flux with DOM events It would be great to use React's synthetic event system, but it has an event set defined and it is not possible to use it to emit custom events.
So let's use DOM events. Some of you will think that DOM and React are not a good match, but this has nothing to do with fast rendering, it is about communicate user interaction to our app.
DOM events will replace Flux dispatcher and actions will react ( oops! I used the r word! ) to those events in order to update the stores. They should be rather called reactions.
So instead of having the traditional Flux diagram
Using events we have something simpler, aligned with some of the most popular flux implementations that don't implement a dispatcher either.
Notice that the line that the line that binds the view with the reactions is dashed. The graph want to remark that Views are independent from the reactions, they will emit events but they don't know anything about reactions.
We need also a hub where our reactions will be listening to the events. I think that the document object would be perfect. The events will be emitted by the DOM node of our components, so the reactions will wait for the events to bubble up through the DOM until they reach the document object, starting the reactions.
The reactions can be coordinated using events too. A reaction can emit new events on finishing, starting new reactions, so we don't need the dispatcher's waitFor
method anymore. In this eventful system, async reactions can work the same way than sync ones, triggering events on finishing. At this point, if we have several reactions dependant from others you can use event stream libraries like RxJS or Bacon.js to make easier event handling if you want ( RxJS and React.js at the same time! ).
A simple example
Let's make a simple counter with a button to increase.
// A simple store
var store = {count: 0};
// Let's create a counter component that we can use
// in all of our projects
var Counter = React.createClass({
render: function(){
return (
<div className="counter">
<span>{this.props.count}</span>
<button onClick={ this.onIncrease }>Increase</button>
</div>
);
},
onIncrease: function(){
// Hit the button will just emit an event
var e = document.createEvent('Event');
e.initEvent('counter', true, true);
this.getDOMNode().dispatchEvent( e );
}
});
// Our reaction
var increaseReaction = function( e ){
store.count++;
// The ideal store would trigger a change event,
// our store is a common object, so we are going to
// refresh the page for demonstration purposes
React.render( <Counter count={ store.count } />, document.body );
}
document.addEventListener( 'counter', increaseReaction, false );
React.render( <Counter count={ store.count } />, document.body );
You can see it working in this JSBin.
Communication children-parent
Using DOM events also make easier the communication between a parent its children components. Sometimes, it is handy that the parent manages the state of its children and, to do so, Facebook recommends to pass a callback to the child component. It is a messy solution, it generates some boilerplate code to store the callbacks and makes you develop your child components depending on functions that live outside them.
// Let's create a counter component that we can use
// in all of our projects
var Counter = React.createClass({
render: function(){
return (
<div className="counter">
<span>{this.props.count}</span>
<button onClick={ this.onIncrease }>Increase</button>
</div>
);
},
onIncrease: function(){
// Hit the button will just emit an event
var e = document.createEvent('Event');
e.initEvent('counter', true, true);
this.getDOMNode().dispatchEvent( e );
}
});
// Our reaction var increaseReaction = function( e ){ store.count++;
// The ideal store would trigger a change event,
// our store is a common object, so we are going to
// refresh the page for demonstration purposes
React.render( <Counter count={ store.count } />, document.body );
} document.addEventListener( 'counter', increaseReaction, false );
React.render( <Counter count={ store.count } />, document.body );
Since we have eventful components now, our parent component can now listen to children events to coordinate them, and the children will just execute its own code. Let's create a selectable list:
// A simple store var store = {count: 0}; // Let's create a counter component that we can use // in all of our projects var Item = React.createClass({ render: function(){ var className = 'item'; if( this.props.selected ) className = ' selected'; return ( <div className={ className }> <span>Item { this.props.index } </span> <button onClick={ this.onSelect }>Select</button> </div> ); }, onSelect: function(){ // Hit the button will just emit an event var e = document.createEvent('Event'); e.initEvent('select', true, true); // Add item index to the event e.detail = this.props.index; this.getDOMNode().dispatchEvent( e ); } }); var List = React.createClass({ getInitialState: function(){ // The list will store the selected state return {selected: -1} }, render: function(){ var items = [], i = 0 ; for(;i<3;i++){ items.push( <Item key={ i } index={ i } selected={ i == this.state.selected } /> ); } return <div className="list">{ items }</div>; }, componentDidMount: function(){ var me = this; // Detect if some child has been selected this.getDOMNode().addEventListener( 'select', function( e ){ me.setState({ selected: e.detail }); }); } }); React.render( <List />, document.body );
This is the list working in a JSBin.
The selected item state is stored in the list instead of in the items, that way it is easy to have just one item selected at a time.
Conclusion Using events for the communication of your components has benefits that can be spotted at the first use. It is not just making our components independent, moving the application logic to the reactions creates a clear C for the MVC pattern, taking it out from the Views and the Model, enforcing a clear separation of concerns.
If you liked the article, I have created a quick helper called flux-reactions to reduce boilerplate of using events on your components, every contribution is welcome!
If you are looking for a great Model to complete the MVC, I would recommend Freezer, because I am the author, but any other immutable store would make your development fly.