Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposing static methods on decorated components #181

Closed
ide opened this issue Jun 26, 2015 · 6 comments
Closed

Exposing static methods on decorated components #181

ide opened this issue Jun 26, 2015 · 6 comments

Comments

@ide
Copy link

ide commented Jun 26, 2015

I want to be able to take a component that has some static methods and decorate it with @connect without needing to change other code... so for example:

class AScreen extends Component {
  static getTitle() { ... }
  static getSceneConfig() { ... }
}

...

// this is a react native thing
<Navigation.Bar
  routeMapper={{ Title: route => { return AScreen.getTitle() } }}
/>

and then I want to connect AScreen to Redux:

@connect(mapper)
class AScreen extends Component { ... }

but now have to change all the callers to read AScreen.DecoratedComponent.getTitle().

This is a problem with decorators as much as it's a problem with Redux but it is a rough edge I encountered when using Redux in practice. The decorator could copy all static methods off of the decorated component and add them to the wrapper if you think that is pragmatic enough to be tasteful.

@johanneslumpe
Copy link
Contributor

There is also the possibility of having a little helper which loops over the decorated components until it finds the root and then calls the method on it:

const callMethod = (Component, method, ...args) => {
  while(Component.DecoratedComponent) {
    Component = Component.DecoratedComponent;
  }

  return Component[method] && Component[method](...args);
};

Then you'd do:

callMethod(AScreen, 'getTitle');

The above could be refined to support multiple methods, e.g. when you define a static method which the decorator defines too. It could then return an array of results.

Copying would make your could just look like nothing changed, which is a good thing, but could - albeit an edge case - possibly overwrite a static method defined on the decorator. (Except when the copying algo is intelligent enough to check whether a method exists and then to wrap it and properly return both results.

@gaearon
Copy link
Contributor

gaearon commented Jun 26, 2015

To be honest I don't think decorating libraries should solve this.
See my comments here:

acdlite/flummox#173 (comment)
acdlite/flummox#173 (comment)

@ide
Copy link
Author

ide commented Jun 26, 2015

@johanneslumpe callMethod works, although I was hoping it'd be possible to keep the regular Class.method() syntax. One fewer concept to think about and explain to people.

@gaearon Yeah, there might not be a graceful solution. I'm not a big fan of the @statics decorator since I want to group related methods together instead of splitting out the static ones because of how decorators work.

I'll mull over this some more since there might be another way to connect the component to redux... a mixin might work well here for example.

@ide ide closed this as completed Jun 26, 2015
@cpsubrian
Copy link

Way late to the party here, but I'm currently experimenting with the following. It's probably super-evil, and I wouldn't expect it to be included in anything expected for general consumption, but its working for my purposes (ReactRouter's willTransitionTo, for example).

Note: I'm not currently worried about cross-browser support. If getOwnPropertyNames becomes an issue I'll probably just whitelist statics that my app uses.

import {connect} from 'react-redux'

/**
 * Wrap redux connect in order to maintain static properties.
 */
function connectWithStaticsDecorator (select, actions, merge) {
  var reduxConnect = connect(select, actions, merge)

  return function connectWithStatics (Component) {
    let statics = {}

    // Babel/ES6 makes static methods non-enumerable.
    Object.getOwnPropertyNames(Component).forEach(function (name) {
      if (typeof Component[name] === 'function') {
        statics[name] = Component[name]
      }
    })

    // Get all enumerable props.
    Object.keys(Component).forEach(function (name) {
      statics[name] = Component[name]
    })

    // Run the redux connect decorator.
    let ConnectedComponent = reduxConnect(Component)

    // Apply statics and return.
    Object.keys(statics).forEach((key) => {
      if (typeof ConnectedComponent[key] === 'undefined') {
        ConnectedComponent[key] = statics[key]
      }
    })

    return ConnectedComponent
  }
}

export default connectWithStaticsDecorator

@ide
Copy link
Author

ide commented Aug 15, 2015

I published a module called autoproxy if anyone's interested. It's a higher-order decorator and you use it like @autoproxy(connect(selector)).

@cpsubrian
Copy link

@ide Very cool, I'll give it a whirl. I should have known you'd solve your own problem and explored your github!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants