Skip to content

Instantly share code, notes, and snippets.

@ide
Last active August 29, 2015 14:24
Show Gist options
  • Save ide/dc90742a5e5dc2cfe464 to your computer and use it in GitHub Desktop.
Save ide/dc90742a5e5dc2cfe464 to your computer and use it in GitHub Desktop.
Issues with decorators and higher-order components
// @connect is a decorator provided by Redux that returns a new class that wraps
// the one defined below. Somewhat unintuitively but understandably, "Example"
// refers to the wrapper component returned from the decorator, not the wrapped
// component defined below.
@connect(data => ({ }))
class Example extends React.Component {
static staticMethod() {}
instanceMethod() {}
render() {}
componentDidMount() {
// This works:
this.instanceMethod();
// This doesn't, because Example refers to the wrapper component:
Example.staticMethod();
// Instead, Redux's @connect explicitly adds a DecoratedComponent property
// to the wrapper component class, so this works:
Example.DecoratedComponent.staticMethod();
// But if you were to have multiple decorators then you'd end up with this:
Example.DecoratedComponent.DecoratedComponent.staticMethod();
}
}
class Root extends React.Component {
render() {
return <Example ref="example" />
}
componentDidMount() {
// This doesn't work, because we have a reference to the wrapper component
// that the @connect decorator made, not the wrapped component
this.refs.example.instanceMethod();
}
}
// The implementation of @connect is in the file below. To be clear, this issue
// isn't specific to Redux. I believe it's a flaw with higher-order components
// and the decorators used to create them.
//
// In a language that supports proxying like Python with __call__, the
// wrapper component can proxy method calls forward to the wrapped component.
// The mixins that worked with React.createClass did not suffer from this
// issue either. In theory they are less pure and isolated but in practice
// they worked pretty well. Perhaps we can write something like this?
//
// class Example extends React.mixin(connect(...), React.Component) {
// }
// Implementation of @connect
import getDisplayName from '../utils/getDisplayName';
import shallowEqualScalar from '../utils/shallowEqualScalar';
export default function createConnectDecorator(React, Connector) {
const { Component } = React;
return function connect(select) {
return DecoratedComponent => class ConnectorDecorator extends Component {
static displayName = `Connector(${getDisplayName(DecoratedComponent)})`;
static DecoratedComponent = DecoratedComponent;
shouldComponentUpdate(nextProps) {
return !shallowEqualScalar(this.props, nextProps);
}
render() {
return (
<Connector select={state => select(state, this.props)}>
{stuff => <DecoratedComponent {...stuff} {...this.props} />}
</Connector>
);
}
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment