23 Jun 2015, 00:00

Extending behavior of React Components by ES6 Decorators & Higher Order Components

Share

About Mixins & Higher Order Components

Sometimes it makes sense to extends a behavior of a component, while mixins are still alive, they’r not recommended anylonger, use Higher Order Components instead.

So instead of doing this:

function StoreMixin(...stores) {
  var Mixin = {
    getInitialState() {
      return this.getStateFromStores(this.props);
    },
    ...
  }
}

var UserProfilePage = React.createClass({
  mixins: [StoreMixin(UserStore)],
  propTypes: {
    userId: PropTypes.number.isRequired
  },
  ...
})

You can do that:

function connectToStores(Component, stores, getStateFromStores) {
  const StoreConnection = React.createClass({
    getInitialState() {
      return getStateFromStores(this.props);
    },
    ...
    render() {
      return <Component {...this.props} {...this.state} />;
    }
  })
}

We just wrap the component and passes some additional props to it, it is a clean and generic solution for extending components without merging behaviors being implemented by React’s mixin merge logic.

You can read more about Higher Order Components here

Extending via ES6 Decorators

Sometimes it makes sense to extends a component without the need to wrap it manually, this technique is common in different frameworks to define components less verbosely when extra functionality is demanded.

Here is a short example how it works, Lets extends the context of our component hierarchy with a print function, so we use it in our render method:

class MyPage extends Component {
    static contextTypes = contextTypes;
    render() {
        return(
            <div>{this.context.print('Hello World')}</div>
        )
    }
}

Assuming MyPage is a child component of App, here is how our App component looks like:

var contextTypes = {
    print: PropTypes.func.isRequired
}

var myContext = {
    print: (m) => (m)
}

@context(contextTypes, myContext)
export default class App extends Component {
    render() {
        return (
            <MyPage/>
        )
    }
}

And here is the code of the decorator:

export default function context(contextTypes, context) {

    return function (DecoratedComponent) {
        return class {
            static childContextTypes = contextTypes;
            getChildContext() {
              return context;
            }
            render() {
              return (
                <DecoratedComponent {...this.props} />
              );
            }
        }
    }
}

The Decorator expects an expression and invokes the function with the given parameters and must return a function that expects the annotated component as a parameter, then we just return a class with some context the wraps our component.

You can read more about Decorators here.

comments powered by Disqus