From 311ef4fa46703ce806593139530b20902a155fa7 Mon Sep 17 00:00:00 2001 From: Ian MacLeod Date: Fri, 8 Jul 2016 16:27:50 -0700 Subject: [PATCH] Have @connect hoist statics (#99) * Have @connect hoist statics Currently, `react-apollo`'s `@connect` strips any custom statics from the components it wraps; this'll preserve them, using the [same method employed by `react-redux`](https://github.com/reactjs/react-redux/blob/master/src/components/connect.js#L365) * Test statics --- global.d.ts | 16 ++++++++++++++++ package.json | 1 + src/connect.tsx | 7 ++++++- test/client/connect/statics.tsx | 34 +++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/client/connect/statics.tsx diff --git a/global.d.ts b/global.d.ts index 708502bc22..cf13b0ba3b 100644 --- a/global.d.ts +++ b/global.d.ts @@ -10,3 +10,19 @@ declare module 'lodash.isequal' { import main = require('~lodash/index'); export = main.isEqual; } + +declare module 'hoist-non-react-statics' { + interface Component { + new(...args:any[]); + } + + /** + * Copies any static properties present on `source` to `target`, excluding those that are specific + * to React. + * + * Returns the target component. + */ + function hoistNonReactStatics(targetComponent:Component, sourceComponent:Component):Component; + namespace hoistNonReactStatics {} + export = hoistNonReactStatics; +} diff --git a/package.json b/package.json index 6485ec711d..45dad464c2 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "uglify-js": "^2.6.2" }, "dependencies": { + "hoist-non-react-statics": "^1.2.0", "invariant": "^2.2.1", "lodash.isequal": "^4.1.1", "lodash.isobject": "^3.0.2", diff --git a/src/connect.tsx b/src/connect.tsx index 83f7ded679..c3a42c580f 100644 --- a/src/connect.tsx +++ b/src/connect.tsx @@ -10,6 +10,7 @@ import isObject = require('lodash.isobject'); import isEqual = require('lodash.isequal'); import invariant = require('invariant'); import assign = require('object-assign'); +import hoistNonReactStatics = require('hoist-non-react-statics'); import { IMapStateToProps, @@ -104,7 +105,8 @@ export default function connect(opts?: ConnectOptions) { const version = nextVersion++; return function wrapWithApolloComponent(WrappedComponent) { - const apolloConnectDisplayName = `Apollo(Connect(${getDisplayName(WrappedComponent)}))`; + // react-redux will wrap this further with Connect(...). + const apolloConnectDisplayName = `Apollo(${getDisplayName(WrappedComponent)})`; class ApolloConnect extends Component { static displayName = apolloConnectDisplayName; @@ -559,6 +561,9 @@ export default function connect(opts?: ConnectOptions) { } + // Make sure we preserve any custom statics on the original component. + hoistNonReactStatics(ApolloConnect, WrappedComponent); + // apply react-redux args from original args const { mapStateToProps, mapDispatchToProps, mergeProps, options } = opts; return ReactReduxConnect( diff --git a/test/client/connect/statics.tsx b/test/client/connect/statics.tsx new file mode 100644 index 0000000000..62ecf2dd88 --- /dev/null +++ b/test/client/connect/statics.tsx @@ -0,0 +1,34 @@ + +import * as React from 'react'; +import * as chai from 'chai'; + +const { expect } = chai; + +import connect from '../../../src/connect'; + +describe('statics', () => { + it('should be preserved', () => { + @connect({}) + class ApolloContainer extends React.Component { + static veryStatic = 'such global'; + }; + + expect(ApolloContainer.veryStatic).to.eq('such global'); + }); + + it('exposes a debuggable displayName', () => { + @connect({}) + class ApolloContainer extends React.Component {} + + expect((ApolloContainer as any).displayName).to.eq('Connect(Apollo(ApolloContainer))'); + }); + + it('honors custom display names', () => { + @connect({}) + class ApolloContainer extends React.Component { + static displayName = 'Foo'; + } + + expect((ApolloContainer as any).displayName).to.eq('Connect(Apollo(Foo))'); + }); +});