Component Composition with ReactJS

Reading Time: 12 minutes

Decoupled and coupled composition.

ReactJS follows the current trend of component oriented Web UI design. Component oriented design is nothing new; in my good old desktop times I wrote applications in Qt, which uses widgets. Java Server Faces, WebForms and others do the same on server side. Nowadays, we can see the same trend hitting client side, like WebComponents, Directives in Angular, FlightJS, and of course ReactJS. In this article I'll talk about different composition types you can realize with ReactJS. Although this article shows how to compose your UI using ReactJS for composition, the ideas behind the composition modalities can be applied for other component driven libraries, or frameworks, too. I distinguish two modalities of composition, which I call Coupled and Decoupled Composition.

A coupled composition means that one component is the owner of another component.The owning component can be considered as a parent, while the owned component becomes a child.This kind of coupling leads to a typical component hierarchy with parent and children components.

decoupled composition is used when none of the components is owner, nor owned.These components exist on the same screen, nevertheless they are not bound or related to each other.

The composition modality defines the way how components communicate which each other.

Coupled Composition

Imagine the following situation: In your application you have a bunch of editors for creating different entities, like customer, product, and so on.
While they differ mainly in type and amount of input fields, they have some parts in common. Generally, they own a very similar structure, with title and a save button. The difference come with the input fields depending on your context. Instead of copying and pasting the code for basic layout, title and button you could create a generic component, where you can inject your concrete editor structure. And this is where the component oriented design starts to rock.

Coupled Composition Example

Coupled Composition Editor Example

 

You could create a frame component, maybe we call it EditorFrame, and reuse this component everytime for each type of editor. So, our composition would be something like this:

render: function () {
     return (
        <EditorFrame title="Customer Editor" editor={<CustomerEditor/>} onSave={this.onCustomerSave}/>
    )
}

 

Well, in this scenario we would use a property to inject our concrete component. Passing a component as property is nice, but lacks a bit of comfort and readabilty. Fortunately, React supports an alternative for composition; instead passing as custom property, you can pass the component as child element, which will look like this:

render: function () {
    return (
        <EditorFrame title="Customer Editor" onSave={this.onCustomerSave}>
           <CustomerEditor/>
        </EditorFrame>
    )
}

 

The differences to the first method, Composition by Property, are minor - technically nearly identical - but I prefer this kind of composition due to its more expressive representation.
And here's the code for the EditorFrame component:

var EditorFrame = React.createClass({

    propTypes: {
        title: React.PropTypes.string.isRequired,
        onSave: React.PropTypes.func.isRequired
    },

    onSave: function (event) {
        this.props.onSave(event);
    },

    render: function () {
        return (
            <div className="panel panel-default">
                <div className="panel-heading">
                    <h3 className="panel-title">{this.props.title}</h3>
                </div>
                <div className="panel-body">
                    // here happens the 'magic', the children property is the injected element CustomerEditor
                   {this.props.children}
                <hr/>
                <button className="btn btn-success pull-right" onClick={this.onSave}>Save</button>
                </div>
            </div>
        )
    }
});

 

This is quite nice, but there is an important detail we have to consider. The passed element aka children property is opaque by definition, in other words not accessible, nor mutable; it is a black box.
For our EditorFrame example this would lead to the following situation: As we do not have access to the properties of the children we wouldn't be able to delegate the Editor's data to the EditorFrame, e.g. for receiving data changes and save them. So, we are forced to union all this in our all surrounding component, like this:

    getInitialState: function () {
        return {customerData: {}};
    },

    // from Editor: handle data changes here
    onCustomerChange: function (data) {
        this.setState({customerData: data});
    },

    // from EditorFrame: save the data
    onCustomerSave: function (event) {
        console.log(JSON.stringify(this.state.customerData));
    },

    render: function () {
        return (
            <EditorFrame title="Customer Editor" onSave={this.onCustomerSave}>
                <CustomerEditor onChange={this.onCustomerChanged}/>
            </EditorFrame>
        )
    }

 

Update

One of the readers asked for the CustomEditor component. So here it is (better late than never):

var CustomerEditor = React.createClass({

            data: {},

            editorDataChanged: function (event) {
                this.data[event.target.name] = event.target.value;
                this.props.onChange(this.data); // propagate data model
            },

            render: function () {
                return (
                    <div>
                        <div className="form-group">
                            <input name="name" className="form-control" type="text" placeholder="Name"
                                   onChange={this.editorDataChanged}/>
                        </div>
                        <div className="form-group">
                            <input name="email" className="form-control" type="text" placeholder="Email"
                                   onChange={this.editorDataChanged}/>
                        </div>
                    </div>
                )
            }
        });

This works, and is quite fine. But we revealed some details of the components implementation, which I prefer to see it hidden; we need to keep track of the customer data, although I'd like to see the EditorFrame doing this for us. But with a little hack we can make it. We tune the example and hide the implementation details of the editor component. We ignore the fact, that the ownee is considered opaque and we use our a priori knowledge of the internal relationship between the EditorFrame and CustomerEditor component and hide the implementation detail of changing data.

And this is how the tuned version looks like:

    getInitialState: function () {
        return {customerData: {}};
    },

    // from EditorFrame: save the data
    onCustomerSave: function (data) {
        console.log(JSON.stringify(data));
    },

    render: function () {
        return (
            <EditorFrame title="Customer Editor" onSave={this.onCustomerSave}>
                <CustomerEditor/>
            </EditorFrame>
        )
    }

 

The trick is, that we hide the onChange property and set it inside the EditorFrame. This is kind of dirty, as the React documentation explicitely advices that properties are considered immutable.

var EditorFrame = React.createClass({

    propTypes: {
        title: React.PropTypes.string.isRequired,
        onSave: React.PropTypes.func.isRequired
    },

    getInitialState : function(){
        return {data : {}};
    },

    change : function(data){
      this.setState( {data : data});
    },

    // this is the trick. Accessing the internal structure of the concrete editor
    setOwneeChangeCallback : function(){
       this.props.children.props.onChange = this.change;
    },

    componentDidMount : function(){
        this.setOwneeChangeCallback();
    },

    // need to re-set the function, as it will be lost otherwise
    componentDidUpdate : function(){
        this.setOwneeChangeCallback();
    },

    onSave: function (event) {
        this.props.onSave(this.state.data);
    },

    render: function () {
        return (
           <div className="panel panel-default">
               <div className="panel-heading">
                  <h3 className="panel-title">{this.props.title}</h3>
               </div>
               <div className="panel-body">
                   {this.props.children}
                   <hr/>
                   <button className="btn btn-success pull-right" onClick={this.onSave}>Save</button>
               </div>
            </div>
       )
    }
});

The trick happens in setOwneeChangeCallback(). We redirect the editor's onChange callback to an internal method, where we save the editor's data. The only method we expose to the final component user is onSave(). In a first version, I discovered that the reference to the change method got lost after a component's update. So, I simply set the onChange property on each update, and it works like a charm.

View Live Demo in DevButze's Dojo

Decoupled Composition

We had the situation where we injected a concrete editor in a generic editor frame. Now, imagine we have a search and/or filter component somewhere on our site, maybe in a sidebar. Our application contains some pages with lists of customers, or products. Wouldn't it be nice, if we could use the search filter component for all of these lists?

decoupled

Decoupled Composition Example

 

Of course, it would. So we could use the concept of coupled composition and construct the site. This can be quite cumbersome, as we need to ask ourselves which element owns which element. How would a generic search component in a sidebar be related to an editor frame in the dynamic content section? It is possible, but needs some effort. An alternative would be very loosely coupled concept, where the components, i.e. Search Bar and Customer List do not know each other. They are living in the same screen, but has no relationships at all.

return React.createClass({

    render: function () {

        return (
            <div>
                <div className="row">
                    <div className="col-xs-12 col-sm-12">
                        <h2>Example of Decoupled Composition</h2>
                        <hr/>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-12 col-sm-12">
                        <SearchBox/>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-12 col-sm-12">
                        <CustomerList/>
                    </div>
                </div>
            </div>
        )
    }
})

To establish a communication between them, we need kind of messages (well, we could use the screen component as mediator, but we won't). Though, decoupled composition works with a kind of global communication system, like a message or event handler. React does not offer any messaging system out-of-the-box. Remember that React is not a framework, but a library without any third-party dependencies. We could develop our own one - which is not difficult, just implementing a kind of observer pattern. But it is faster to take a a third-party library, for example EventEmitter.

The idea is, that the search bar sends a message each time the search term changes, while the customer list is listening to messages. Using EventEmitter is very simple, and using it with React is piece of cake. Only two tiny details we should consider. We must take care, that the component unregisters itself when being unmounted. The EventEmitter doesn't know about mounted or unmounted state. If we change the state of an unmounted component we will get an exception. On the other hand, we do not know how the EventEmitter processes the messages. Let's assume that the EventEmitter broadcasts the messages asynchronously. Therefore, it may be possible, that React has already unmounted a component, before the messages was being delivered. This leads to another exception. But with React we can check, whether a component is mounted or not using isMounted().

And here comes the entire code for the customer list component:

var CustomerList = React.createClass({

    // Mocked customers for demonstration purposes
    createCustomers: function () {
        var N = 400;
        var customers = [];
        for (var i = 0; i < N; ++i) {
            customers.push({
                name: "Customer " + i,
                email: "user_" + i + "@customer.com"
            })
        }
        return customers;
    },

    _allCustomers: [],

    getInitialState: function () {
        this._allCustomers = this.createCustomers();
        return {customers: []};
    },

    componentDidMount: function () {
        // start listening to EventEmitter, when component was mounted.
        $event.addListener('search', this.onSearch);
    },

    componentWillUnmount: function () {
        // remove the listener, before unmounting
        $event.removeListener('search', this.onSearch);
    },

    // triggered by search event
    onSearch: function (searchTerm) {
        // here we protect ourselves against deferred/delayed events
        if (!this.isMounted()) return; 

        var term = searchTerm.trim().toLowerCase();

        var foundCustomers = [];
        if (term.length > 0) {
            var foundCustomers = this._allCustomers.filter(function (customer) {
                return customer.name.toLowerCase().match(term) || customer.email.toLowerCase().match(term);
            });
        }

        this.setState({customers: foundCustomers});

    },

    render: function () {

        var customerCards = this.state.customers.length === 0 ?
            <h4>Please, search for items!</h4>
            :
            this.state.customers.map(function (customer, index) {
                return <CustomerCard key={index} name={customer.name} email={customer.email}/>;
            });

        return (
            <div className="panel panel-default">
                <div className="panel-heading">
                    <h3 className="panel-title">Customers</h3>
                </div>
                <div className="panel-body">
                    <div className="list-group">
                        {customerCards}
                    </div>
                </div>
            </div>
        )
    }
});

The search bar component is very simple

var SearchBox = React.createClass(
    {
        getInitialState: function () {
            return {};
        },

        onSearchTermChanged: function (event) {
            var searchTerm = event.target.value.trim();
            // here we broadcast the message with our searchterm as payload
            $event.emitEvent('search', [searchTerm]);
        },

        render: function () {
            return (
                <div className="panel panel-default">
                    <div className="panel-body">
                        <div className="input-group">
                            <span className="input-group-addon" id="basic-addon1"><span
                                className="glyphicon glyphicon-search" aria-hidden="true"></span></span>
                            <input type="search" placeholder="Enter Search Text" className="form-control"
                                   onChange={this.onSearchTermChanged}/>
                        </div>
                    </div>
                </div>
            )
        }
    }
);

 

Aparently, the decoupled composition is less complicated than coupled composition. But, when the application grows, the simple event-system gets more difficult to be controlled. This is a serious problem, and has a huge impact on the code's maintainability. Therefore, decoupled composition should be used only when really needed. A huge advantage of this composition modality is the possibility to easily broadcast messages to more than one component. When you need to change the state of several components, the event system could be very handy.

View Live Demo in DevButze's Dojo

Finally

In this article I presented the differences between coupled and decoupled composition. The coupled composition needs a bit more design work and also some time for adoption, but allows to create a hierarchy of encapsuled and interoperable components with owner-ownee-relationships. This is the most usual way to build up a UI with React, but under some circumstances, it can be easier or even necessary to loosely couple non-related components using message based communication, i.e. when need to broadcast information to many components, or when the UI offers a global component with cross-context interoperability, like a sites search bar. On the other hand, the decoupled composition is proportionally difficult to maintain, when the UIs complexity grows .Although, the presented examples are based on ReactJS, the explained composition modalities can be applied in one way or another with other component based/oriented libraries, or frameworks.

 

These and more examples are available on github

Facebooktwittergoogle_plusredditpinterestlinkedin

5 comments

  1. Hi! I see really well designed and valuable blog. Keep going and always share your knowledge 🙂

    1. Thanks for the feedback. Although, time is a very limited resource I'll keep going as much as I can. Still having a lot of stuff in my pipeline.

  2. Hi Oliver ! Could you please add the missing code (the surrounding component code is incomplete) and the full code of the CustomerEditor is also missing. I also don't know if you mistyped :

    and should be :

    It would be nice to have the complete example just before the "tuned version". I think there are some things missing that are not trivial to understand without the full code. I still do not see why ReactJS is not capable of providing full bidirectional communication between components (parent as children) as you would normally have when doing composition.

    Thanks,
    Lucas.

    1. Hi Lucas,

      finally, I added the CustomerEditor component. If you need the entire example you can get this and several other ReactJS examples here. Note, that this article (and the code at github) is already a few days old, but should still work. Although, most React-Apps use the ES2015 syntax (class) and Browserify/WebPack. Don't hesitate to ask more, if you have doubts. I'll try to answer ASAP 🙂

  3. Maybe this better
    var extendedChildren = React.Children.map(this.props.children, (node, index) => {
    var extendedNode = React.cloneElement(node, {
    onChange : this.change.bind(this, node.props.value)
    });
    return extendedNode;
    });
    then
    setOwneeChangeCallback : function(){
    this.props.children.props.onChange = this.change;
    },

Leave a Reply

Your email address will not be published. Required fields are marked *