-
-
Save ide/dc90742a5e5dc2cfe464 to your computer and use it in GitHub Desktop.
Issues with decorators and higher-order components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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) { | |
// } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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