Skip to content

Get wrapped instance #10

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

Merged
merged 3 commits into from
Aug 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,9 @@ The returned component accepts a `theme` and `composeTheme` apart from the prop

- `Identifier` *(String)* used to provide a unique identifier to the component that will be used to get a theme from context.
- `[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.
- `[options]` (*Object*) is an option object that for now only accepts one value: `composeTheme` which accepts:
- `deeply` to deeply merge themes.
- `softly` to softly merge themes.
- `false` to disable theme merging.
- `[options]` (*Object*) If specified it allows to customize the behavior:
- [`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.
- [`withRef = false`] *(Boolean)* if true, stores a ref to the wrapped component instance and makes it available via `getWrappedInstance()` method. Defaults to false.

## About

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,8 @@
"license": "MIT",
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0"
},
"dependencies": {
"invariant": "^2.2.1"
}
}
43 changes: 34 additions & 9 deletions src/components/themr.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { Component, PropTypes } from 'react'
import invariant from 'invariant'

const COMPOSE_DEEPLY = 'deeply'
const COMPOSE_SOFTLY = 'softly'
const DONT_COMPOSE = false

const DEFAULT_OPTIONS = {
composeTheme: COMPOSE_DEEPLY
composeTheme: COMPOSE_DEEPLY,
withRef: false
}

export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (ThemedComponent) => {
const { composeTheme: optionComposeTheme } = options
export default (componentName, localTheme, options = {}) => (ThemedComponent) => {
const { composeTheme: optionComposeTheme, withRef: optionWithRef } = { ...DEFAULT_OPTIONS, ...options }
validateComposeOption(optionComposeTheme)
return class Themed extends Component {
static displayName = `Themed ${ThemedComponent.name}`;
Expand All @@ -27,6 +29,15 @@ export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (Themed
composeTheme: optionComposeTheme
}

getWrappedInstance() {
invariant(optionWithRef,
'To access the wrapped instance, you need to specify ' +
'{ withRef: true } as the third argument of the themr() call.'
)

return this.refs.wrappedInstance
}

getThemeNotComposed() {
if (this.props.theme) return this.props.theme
if (localTheme) return localTheme
Expand All @@ -47,12 +58,26 @@ export default (componentName, localTheme, options = DEFAULT_OPTIONS) => (Themed

render() {
const { composeTheme, ...rest } = this.props
return React.createElement(ThemedComponent, {
...rest,
theme: composeTheme
? this.getTheme()
: this.getThemeNotComposed()
})
let renderedElement

if (optionWithRef) {
renderedElement = React.createElement(ThemedComponent, {
...rest,
ref: 'wrappedInstance',
theme: composeTheme
? this.getTheme()
: this.getThemeNotComposed()
})
} else {
renderedElement = React.createElement(ThemedComponent, {
...rest,
theme: composeTheme
? this.getTheme()
: this.getThemeNotComposed()
})
}

return renderedElement
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions test/components/themr.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,55 @@ describe('Themr decorator function', () => {
const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough)
expect(stub.props.theme).toEqual({})
})

it('should throw when trying to access the wrapped instance if withRef is not specified', () => {
const theme = { Container: { foo: 'foo_1234' } }

@themr('Container')
class Container extends Component {
render() {
return <Passthrough {...this.props} />
}
}

const tree = TestUtils.renderIntoDocument(
<ProviderMock theme={theme}>
<Container />
</ProviderMock>
)

const container = TestUtils.findRenderedComponentWithType(tree, Container)
expect(() => container.getWrappedInstance()).toThrow(
/To access the wrapped instance, you need to specify \{ withRef: true \} as the third argument of the themr\(\) call\./
)
})

it('should return the instance of the wrapped component for use in calling child methods', () => {
const someData = {
some: 'data'
}

class Container extends Component {
someInstanceMethod() {
return someData
}

render() {
return <Passthrough />
}
}

const decorator = themr('Component', null, { withRef: true })
const Decorated = decorator(Container)

const tree = TestUtils.renderIntoDocument(
<Decorated />
)

const decorated = TestUtils.findRenderedComponentWithType(tree, Decorated)

expect(() => decorated.someInstanceMethod()).toThrow()
expect(decorated.getWrappedInstance().someInstanceMethod()).toBe(someData)
expect(decorated.refs.wrappedInstance.someInstanceMethod()).toBe(someData)
})
})