Skip to content

Update react api usage to react 16 strict mode compatible #98

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@
"fbjs": "^0.8.12",
"jsdom": "^9.8.3",
"mocha": "^3.3.0",
"prop-types": "^15.5.9",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"prop-types": "^15.7.2",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"rimraf": "^2.6.1",
"sinon": "^1.17.6"
},
Expand All @@ -64,7 +64,7 @@
},
"license": "MIT",
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0-0"
"react": "^16.3.0-0"
},
"typings": "./index.d.ts"
}
38 changes: 9 additions & 29 deletions src/components/ThemeProvider.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,10 @@
import { Children, Component } from 'react'
import PropTypes from 'prop-types'
import themrShape from '../utils/themr-shape'

export default class ThemeProvider extends Component {
static propTypes = {
children: PropTypes.element.isRequired,
theme: PropTypes.object.isRequired
}

static defaultProps = {
theme: {}
}

static childContextTypes = {
themr: themrShape.isRequired
}

getChildContext() {
return {
themr: {
theme: this.props.theme
}
}
}

render() {
return Children.only(this.props.children)
}
import React, { Children } from 'react'
import { ThemeContext } from './themr'

export default function ThemeProvider(props) {
return (
<ThemeContext.Provider value={{ theme: props.theme }}>
{Children.only(props.children)}
</ThemeContext.Provider>
)
}
138 changes: 71 additions & 67 deletions src/components/themr.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import hoistNonReactStatics from 'hoist-non-react-statics'
import invariant from 'invariant'

export const ThemeContext = React.createContext({ theme: {} })

/**
* @typedef {Object.<string, TReactCSSThemrTheme>} TReactCSSThemrTheme
*/
Expand Down Expand Up @@ -50,16 +52,56 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
localTheme
}

function getNamespacedTheme(props) {
const { themeNamespace, theme } = props
if (!themeNamespace) return theme
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
'themeNamespace prop should be used only with theme prop.')

return Object.keys(theme)
.filter(key => key.startsWith(themeNamespace))
.reduce((result, key) => ({ ...result, [removeNamespace(key, themeNamespace)]: theme[key] }), {})

}

function getContextTheme(props) {
return props.contextTheme
? props.contextTheme.theme[config.componentName]
: {}
}

function getThemeNotComposed(props) {
if (props.theme) return getNamespacedTheme(props)
if (config.localTheme) return config.localTheme
return getContextTheme(props)
}

function getTheme(props) {
return props.composeTheme === COMPOSE_SOFTLY
? {
...getContextTheme(props),
...config.localTheme,
...getNamespacedTheme(props)
}
: themeable(
themeable(getContextTheme(props), config.localTheme),
getNamespacedTheme(props)
)
}

function calcTheme(props) {
const { composeTheme } = props
return composeTheme
? getTheme(props)
: getThemeNotComposed(props)
}

/**
* @property {{wrappedInstance: *}} refs
*/
class Themed extends Component {
static displayName = `Themed${ThemedComponent.name}`;

static contextTypes = {
themr: PropTypes.object
}

static propTypes = {
...ThemedComponent.propTypes,
composeTheme: PropTypes.oneOf([ COMPOSE_DEEPLY, COMPOSE_SOFTLY, DONT_COMPOSE ]),
Expand All @@ -75,84 +117,46 @@ export default (componentName, localTheme, options = {}) => (ThemedComponent) =>
mapThemrProps: optionMapThemrProps
}

constructor(...args) {
super(...args)
this.theme_ = this.calcTheme(this.props)
}

getWrappedInstance() {
invariant(true,
'DEPRECATED: To access the wrapped instance, you have to pass ' +
'{ innerRef: fn } and retrieve with a callback ref style.'
)

return this.refs.wrappedInstance
}

getNamespacedTheme(props) {
const { themeNamespace, theme } = props
if (!themeNamespace) return theme
if (themeNamespace && !theme) throw new Error('Invalid themeNamespace use in react-css-themr. ' +
'themeNamespace prop should be used only with theme prop.')

return Object.keys(theme)
.filter(key => key.startsWith(themeNamespace))
.reduce((result, key) => ({ ...result, [removeNamespace(key, themeNamespace)]: theme[key] }), {})
}

getThemeNotComposed(props) {
if (props.theme) return this.getNamespacedTheme(props)
if (config.localTheme) return config.localTheme
return this.getContextTheme()
}

getContextTheme() {
return this.context.themr
? this.context.themr.theme[config.componentName]
: {}
}

getTheme(props) {
return props.composeTheme === COMPOSE_SOFTLY
? {
...this.getContextTheme(),
...config.localTheme,
...this.getNamespacedTheme(props)
}
: themeable(
themeable(this.getContextTheme(), config.localTheme),
this.getNamespacedTheme(props)
)
}
getWrappedInstance() {
invariant(true,
'DEPRECATED: To access the wrapped instance, you have to pass ' +
'{ innerRef: fn } and retrieve with a callback ref style.'
)

calcTheme(props) {
const { composeTheme } = props
return composeTheme
? this.getTheme(props)
: this.getThemeNotComposed(props)
return this.refs.wrappedInstance
}

componentWillReceiveProps(nextProps) {
lastProps = {};
calcTheme = (props) => {
if (
nextProps.composeTheme !== this.props.composeTheme ||
nextProps.theme !== this.props.theme ||
nextProps.themeNamespace !== this.props.themeNamespace
props.composeTheme !== this.lastProps.composeTheme ||
props.theme !== this.lastProps.theme ||
props.themeNamespace !== this.lastProps.themeNamespace ||
props.contextTheme !== this.lastProps.contextTheme
) {
this.theme_ = this.calcTheme(nextProps)
this.lastProps = props
this.theme_ = calcTheme(props)
}
return this.theme_
}

render() {
return React.createElement(
ThemedComponent,
this.props.mapThemrProps(this.props, this.theme_)
return (
<ThemeContext.Consumer>
{contextTheme => (
<ThemedComponent {...this.props.mapThemrProps(this.props, this.calcTheme({ ...this.props, contextTheme }))} />
)}
</ThemeContext.Consumer>
)
}
}

Themed[THEMR_CONFIG] = config

return hoistNonReactStatics(Themed, ThemedComponent)
return hoistNonReactStatics(
Themed,
ThemedComponent
)
}

/**
Expand Down
24 changes: 13 additions & 11 deletions test/components/ThemeProvider.spec.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React, { Component } from 'react'
import { render } from 'react-dom'
import expect from 'expect'
import PropTypes from 'prop-types'
import TestUtils from 'react-dom/test-utils'
import { ThemeProvider } from '../../src/index'
import { ThemeContext } from '../../src/components/themr'

describe('ThemeProvider', () => {
class Child extends Component {
render() {
return <div />
return (
<ThemeContext.Consumer>
{data => JSON.stringify(data)}
</ThemeContext.Consumer>
)
}
}

Child.contextTypes = {
themr: PropTypes.object.isRequired
}

it('enforces a single child', () => {
const theme = {}

Expand Down Expand Up @@ -46,7 +47,7 @@ describe('ThemeProvider', () => {
})

it('should add the theme to the child context', () => {
const theme = {}
const theme = { foo: 'bar' }

TestUtils.renderIntoDocument(
<ThemeProvider theme={theme}>
Expand All @@ -55,15 +56,16 @@ describe('ThemeProvider', () => {
)

const spy = expect.spyOn(console, 'error')
const tree = TestUtils.renderIntoDocument(
const node = document.createElement('div')
render(
<ThemeProvider theme={theme}>
<Child />
</ThemeProvider>
</ThemeProvider>,
node
)
spy.destroy()
expect(spy.calls.length).toBe(0)

const child = TestUtils.findRenderedComponentWithType(tree, Child)
expect(child.context.themr.theme).toBe(theme)
expect(JSON.parse(node.innerHTML)).toEqual({ theme })
})
})
34 changes: 4 additions & 30 deletions test/components/themr.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import expect from 'expect'
import React, { Children, Component } from 'react'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TestUtils from 'react-dom/test-utils'
import sinon from 'sinon'
import { render } from 'react-dom'
import shallowEqual from 'fbjs/lib/shallowEqual'
import { themr, themeable } from '../../src/index'
import { ThemeProvider } from '../../src/index'

describe('Themr decorator function', () => {
class Passthrough extends Component {
Expand All @@ -15,40 +16,13 @@ describe('Themr decorator function', () => {
}
}

class ProviderMock extends Component {
static childContextTypes = {
themr: PropTypes.object.isRequired
}

getChildContext() {
return { themr: { theme: this.props.theme } }
}

class ProviderMock extends Component {
render() {
return Children.only(this.props.children)
return <ThemeProvider {...this.props} />
}
}

it('passes a context theme object using the component\'s context', () => {
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.context.themr.theme).toBe(theme)
})

it('passes a context theme object using the component\'s theme prop', () => {
const containerTheme = { foo: 'foo_1234' }
const theme = { Container: containerTheme }
Expand Down
Loading