Skip to content

Commit eebd3dd

Browse files
authored
Merge pull request javivelasco#10 from javivelasco/get-wrapper-instance
Get wrapper instance
2 parents c1433b9 + e4c087e commit eebd3dd

File tree

4 files changed

+91
-13
lines changed

4 files changed

+91
-13
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,9 @@ The returned component accepts a `theme` and `composeTheme` apart from the prop
162162

163163
- `Identifier` *(String)* used to provide a unique identifier to the component that will be used to get a theme from context.
164164
- `[defaultTheme]` (*Object*) is classname object resolved from CSS modules. It will be used as the default theme to calculate a new theme that will be passed to the component.
165-
- `[options]` (*Object*) is an option object that for now only accepts one value: `composeTheme` which accepts:
166-
- `deeply` to deeply merge themes.
167-
- `softly` to softly merge themes.
168-
- `false` to disable theme merging.
165+
- `[options]` (*Object*) If specified it allows to customize the behavior:
166+
- [`composeTheme = 'deeply'`] *(String)* allows to customize the way themes are merged or to disable merging completely. The accepted values are `deeply` to deeply merge themes, `softly` to softly merge themes and `false` to disable theme merging.
167+
- [`withRef = false`] *(Boolean)* if true, stores a ref to the wrapped component instance and makes it available via `getWrappedInstance()` method. Defaults to false.
169168

170169
## About
171170

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,8 @@
5656
"license": "MIT",
5757
"peerDependencies": {
5858
"react": "^0.14.0 || ^15.0.0-0"
59+
},
60+
"dependencies": {
61+
"invariant": "^2.2.1"
5962
}
6063
}

src/components/themr.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React, { Component, PropTypes } from 'react'
2+
import invariant from 'invariant'
23

34
const COMPOSE_DEEPLY = 'deeply'
45
const COMPOSE_SOFTLY = 'softly'
56
const DONT_COMPOSE = false
67

78
const DEFAULT_OPTIONS = {
8-
composeTheme: COMPOSE_DEEPLY
9+
composeTheme: COMPOSE_DEEPLY,
10+
withRef: false
911
}
1012

11-
export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (ThemedComponent) => {
12-
const { composeTheme: optionComposeTheme } = options
13+
export default (componentName, localTheme, options = {}) => (ThemedComponent) => {
14+
const { composeTheme: optionComposeTheme, withRef: optionWithRef } = { ...DEFAULT_OPTIONS, ...options }
1315
validateComposeOption(optionComposeTheme)
1416
return class Themed extends Component {
1517
static displayName = `Themed ${ThemedComponent.name}`;
@@ -27,6 +29,15 @@ export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (Themed
2729
composeTheme: optionComposeTheme
2830
}
2931

32+
getWrappedInstance() {
33+
invariant(optionWithRef,
34+
'To access the wrapped instance, you need to specify ' +
35+
'{ withRef: true } as the third argument of the themr() call.'
36+
)
37+
38+
return this.refs.wrappedInstance
39+
}
40+
3041
getThemeNotComposed() {
3142
if (this.props.theme) return this.props.theme
3243
if (localTheme) return localTheme
@@ -47,12 +58,26 @@ export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (Themed
4758

4859
render() {
4960
const { composeTheme, ...rest } = this.props
50-
return React.createElement(ThemedComponent, {
51-
...rest,
52-
theme: composeTheme
53-
? this.getTheme()
54-
: this.getThemeNotComposed()
55-
})
61+
let renderedElement
62+
63+
if (optionWithRef) {
64+
renderedElement = React.createElement(ThemedComponent, {
65+
...rest,
66+
ref: 'wrappedInstance',
67+
theme: composeTheme
68+
? this.getTheme()
69+
: this.getThemeNotComposed()
70+
})
71+
} else {
72+
renderedElement = React.createElement(ThemedComponent, {
73+
...rest,
74+
theme: composeTheme
75+
? this.getTheme()
76+
: this.getThemeNotComposed()
77+
})
78+
}
79+
80+
return renderedElement
5681
}
5782
}
5883
}

test/components/themr.spec.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,55 @@ describe('Themr decorator function', () => {
273273
const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough)
274274
expect(stub.props.theme).toEqual({})
275275
})
276+
277+
it('should throw when trying to access the wrapped instance if withRef is not specified', () => {
278+
const theme = { Container: { foo: 'foo_1234' } }
279+
280+
@themr('Container')
281+
class Container extends Component {
282+
render() {
283+
return <Passthrough {...this.props} />
284+
}
285+
}
286+
287+
const tree = TestUtils.renderIntoDocument(
288+
<ProviderMock theme={theme}>
289+
<Container />
290+
</ProviderMock>
291+
)
292+
293+
const container = TestUtils.findRenderedComponentWithType(tree, Container)
294+
expect(() => container.getWrappedInstance()).toThrow(
295+
/To access the wrapped instance, you need to specify \{ withRef: true \} as the third argument of the themr\(\) call\./
296+
)
297+
})
298+
299+
it('should return the instance of the wrapped component for use in calling child methods', () => {
300+
const someData = {
301+
some: 'data'
302+
}
303+
304+
class Container extends Component {
305+
someInstanceMethod() {
306+
return someData
307+
}
308+
309+
render() {
310+
return <Passthrough />
311+
}
312+
}
313+
314+
const decorator = themr('Component', null, { withRef: true })
315+
const Decorated = decorator(Container)
316+
317+
const tree = TestUtils.renderIntoDocument(
318+
<Decorated />
319+
)
320+
321+
const decorated = TestUtils.findRenderedComponentWithType(tree, Decorated)
322+
323+
expect(() => decorated.someInstanceMethod()).toThrow()
324+
expect(decorated.getWrappedInstance().someInstanceMethod()).toBe(someData)
325+
expect(decorated.refs.wrappedInstance.someInstanceMethod()).toBe(someData)
326+
})
276327
})

0 commit comments

Comments
 (0)