Decorator Components with ReactJS

Reading Time: 7 minutes
Decorator Pattern in OO

Decorator Pattern in OO

Today I'd like to talk about a composition pattern, which can be best denominated as Decorator. Like in object oriented programming the decorator wraps a component and extends its functionality, usually without changing the basic behaviour. With the composition capabilities of ReactJS it is possible to adopt this concept and allows to create components that extends other components, for example by visual effects, or pure functional behaviour like logging activities. In this article I'll present a simple use case demonstrating the pattern.

Ok, here we go. To apply a decorator I use the coupled composition modality, which looks like this using ReactJS:

<DecoratoringComponent>
    <DecoratedComponent/>
</DecoratingComponent>

CSSTransitionGroup - An addon for animated components

A nice example for a decorator is an animated component. The idea is to let an item appear with animation. In this case, this is simple as we can use an addon component from React: CSSTransitionGroup.The CSSTransitionGroup is very easy to use. Just pass a name for a css class and put the component to be animated inside the CSSTransitionGroup. Furthermore, you need to define the CSS to be transitioned, for example:

/* Animation Example */
.example-enter{
  opacity: 0.1;
  transition: all .5s ease-in;
  transform: translateX(-1000px);
}

.example-enter.example-enter-active{
  transform: translateX(0px);
  opacity: 1;

}

.example-leave {
  opacity: 1;
  transform: scale(1,1);
  transition: all .5s ease-in;
}

.example-leave.example-leave-active {
  opacity: 0.01;
  transform: scale(0,0);
}

The convention of the CSS class names is defined by the CSSTransitionGroup, and is described at the React Project Site. And this is how our render method using CSSTransitionGroup could look like:

render: function () {

    var items = this.state.items.map(function (item, index) {
        return (
            <a className="list-group-item" key={index}>
                <span className="glyphicon glyphicon-remove-circle pull-right"
                      aria-hidden="true" onClick={this.removeItem.bind(this, index)}/>
                <h4 className="list-group-item-heading">{item}</h4>
            </a>
        )
    }, this);

    return (
        <div className="row">
            <div className="col-xs-6 col-sm-6">
                <div className="panel panel-default">
                    <div className="panel-heading">
                        <h3 className="panel-title">To Do Items</h3>
                    </div>
                    <div className="panel-body">
                        <div className="input-group">
                            <input ref="itemText" type="text" className="form-control"
                                   placeholder="Enter Item Text..."/>
                  <span className="input-group-btn">
                    <button className="btn btn-default" type="button" onClick={this.addItem}>
                        Add Item
                    </button>
                  </span>
                        </div>
                        <hr/>
                        <div className="list-group">
                            <CSSTransitionGroup transitionName="example">
                                {items}
                            </CSSTransitionGroup>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
}

Live Demo

Creating a Logger decorator

So, that's the way a decorator can be used. How we can create such a decorator? While the CSSTransitionGroup is a bit too complex to show off here, I'll choose another use case where the decorator pattern could be applied. Let's create a generic logging component, which logs information about a child component. The idea is to wrap any component with our decorator we want to log, i.e. for development purposes. The usage is like this:

render: function () {

    return (
        <div className="row">
            <div className="col-xs-6 col-sm-6">
                <Logger onLog={this.onLog}>
                    <InnerComponent title="Input Field"/>
                </Logger>
            </div>
        </div>
    )
}

Our Logger should be capable to log property and state changes for example to the console. Additionally, we want the logger returning each log event for custom purposes. Probably, the latter is the easiest thing: just pass a function as property, which will be our callback. For our logger it should be sufficient to inform about mounting, updating and unmounting a component. So a first stub may look like this:

var Logger = React.createClass({
    
    propTypes : {
        onLog : React.PropTypes.func
    },

    createTimestamp: function () {
        var d = new Date();
        return d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + '.' + d.getMilliseconds();
    },

    log: function (fname, msg) {
        // simple console logger... but we could extend it at our will, i.e using IndexedDB, or LocalStorage
        var logMsg = 'L[' + this.createTimestamp() + '] ' + fname;
        if(msg){
            logMsg += ': ' + msg;
        }
        console.log(logMsg);

        // propagating to owner
        if (this.props.onLog) {
            this.props.onLog(logMsg);
        }
    },

    componentWillMount: function () {
        this.log('Mounting Component');
    },

    componentDidMount: function () {
        this.log('Component mounted');
    },

    componentWillUnmount: function () {
        this.log('Unmounting Component');
    },

    render: function () {
        // we have no own rendering...
        return this.props.children;
    }
});

Well, this Logger resolves a good part of our requisites. A typical pattern for decorators is returning its wrapped component in the render function directly. As we do not render the logger itself, but only the coupled component, we return the child component using the special ReactJS property .children. To pass a log event up the component hierarchy we simply use the onLog property for propagation. This is the pure React-way. As we can see, we use three of the life cycle functions for logging. But hey, what about componentDidUpdate() method? And what about the properties and the state? Wait, one after another.

We could override the Loggers componentDidUpdate() method maybe like this:

componentDidUpdate: function () {
    this.log('Updated Component - Props', JSON.stringify(this.props.children.props));
}

This is quite a good idea, and would work; at least for the properties, but this won't work for the child's state. Because the state is not as easy accessible as properties are, i.e. there's is no this.props.children.state. This is by design, and it's good design. Usually, we should not access a child's state. In our case it is viable to access it. The way to get a state of a child is to intercept the child's life cycle method, i.e. componentDidUpdate(). We need to implement a hook. And this works like this:

componentDidMount: function () {
    var state = this.props.children.type.prototype.getInitialState();
    var props = this.props.children.props;
    this.log('Component Mounted - Initial Props', JSON.stringify(props));
    this.log('Component Mounted - Initial State', JSON.stringify(state));

    // Here starts the magic.
    // We are deeply inspecting the child component, and hooking in its lifecycle methods.
    // override children component function - so, we are really decorating
    // Hint: we  need to backup the original function, and return to its prior 
    // behavior when Logger gets unmounted. We do this using a member variable
    this.funcStore.didUpdate = this.props.children.type.prototype.componentDidUpdate;
    this.props.children.type.prototype.componentDidUpdate = function (prevProps, prevState) {
        if (this.funcStore.didUpdate) {
            this.funcStore.didUpdate(prevProps, prevState);
        }
     this.log('Updated Component - Props', JSON.stringify(prevProps));
     this.log('Updated Component - State', JSON.stringify(prevState));
    }.bind(this);
}

In this case, we hooked in at mounting phase already. The magic is the this.props.children.type.prototype object. There we can hook into the components methods. The changing state and properties are passed as arguments to the componentDidUpdate() function. This is, how we access the state and properties, each time when the children component is updated. One detail we need to consider. We need to reset the component behavior when the Logger (and the child) gets unmounted. That's why a backup of the original function is necessary. And here comes the resetter:

componentWillUnmount: function () {
    this.log('Unmounting Component');
    // resetting its prior behaviour 
    this.props.children.type.prototype.componentDidUpdate = this.funcStore.didUpdate;
},

That's all. Finally, we have a working logger. Note, that this Logger works only for a single child component. The full code is available on github and the Live Demo in my Dojo

Facebooktwittergoogle_plusredditpinterestlinkedin

Leave a Reply

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