Skip to content

Commit

Permalink
Merge pull request #3440 from gaearon/context-mixins
Browse files Browse the repository at this point in the history
Implement ContextUtils as mixins
  • Loading branch information
taion committed May 9, 2016
2 parents 8d10a89 + c57529b commit dcb1b51
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 57 deletions.
78 changes: 36 additions & 42 deletions modules/ContextUtils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PropTypes } from 'react'
import { PropTypes } from 'react'

// Works around issues with context updates failing to propagate.
// https://github.com/facebook/react/issues/2517
Expand All @@ -13,61 +13,61 @@ function makeContextName(name) {
return `@@contextSubscriber/${name}`
}

export function createContextProvider(name) {
export function ContextProvider(name) {
const contextName = makeContextName(name)
const listenersKey = `${contextName}/listeners`
const eventIndexKey = `${contextName}/eventIndex`
const subscribeKey = `${contextName}/subscribe`

const ContextProvider = React.createClass({
propTypes: {
children: PropTypes.node.isRequired
},

return {
childContextTypes: {
[contextName]: contextProviderShape.isRequired
},

getChildContext() {
return {
[contextName]: {
subscribe: this.subscribe,
eventIndex: this.eventIndex
eventIndex: this[eventIndexKey],
subscribe: this[subscribeKey]
}
}
},

componentWillMount() {
this.eventIndex = 0
this.listeners = []
this[listenersKey] = []
this[eventIndexKey] = 0
},

componentWillReceiveProps() {
this.eventIndex++
this[eventIndexKey]++
},

componentDidUpdate() {
this.listeners.forEach(listener => listener(this.eventIndex))
this[listenersKey].forEach(listener =>
listener(this[eventIndexKey])
)
},

subscribe(listener) {
[subscribeKey](listener) {
// No need to immediately call listener here.
this.listeners.push(listener)
this[listenersKey].push(listener)

return () => {
this.listeners = this.listeners.filter(item => item !== listener)
this[listenersKey] = this[listenersKey].filter(item =>
item !== listener
)
}
},

render() {
return this.props.children
}
})

return ContextProvider
}
}

export function connectToContext(WrappedComponent, name) {
export function ContextSubscriber(name) {
const contextName = makeContextName(name)
const lastRenderedEventIndexKey = `${contextName}/lastRenderedEventIndex`
const handleContextUpdateKey = `${contextName}/handleContextUpdate`
const unsubscribeKey = `${contextName}/unsubscribe`

const ContextSubscriber = React.createClass({
return {
contextTypes: {
[contextName]: contextProviderShape
},
Expand All @@ -78,7 +78,7 @@ export function connectToContext(WrappedComponent, name) {
}

return {
lastRenderedEventIndex: this.context[contextName].eventIndex
[lastRenderedEventIndexKey]: this.context[contextName].eventIndex
}
},

Expand All @@ -87,8 +87,8 @@ export function connectToContext(WrappedComponent, name) {
return
}

this.unsubscribe = this.context[contextName].subscribe(
this.handleContextUpdate
this[unsubscribeKey] = this.context[contextName].subscribe(
this[handleContextUpdateKey]
)
},

Expand All @@ -98,29 +98,23 @@ export function connectToContext(WrappedComponent, name) {
}

this.setState({
lastRenderedEventIndex: this.context[contextName].eventIndex
[lastRenderedEventIndexKey]: this.context[contextName].eventIndex
})
},

componentWillUnmount() {
if (!this.unsubscribe) {
if (!this[unsubscribeKey]) {
return
}

this.unsubscribe()
this.unsubscribe = null
this[unsubscribeKey]()
this[unsubscribeKey] = null
},

handleContextUpdate(eventIndex) {
if (eventIndex !== this.state.lastRenderedEventIndex) {
this.setState({ lastRenderedEventIndex: eventIndex })
[handleContextUpdateKey](eventIndex) {
if (eventIndex !== this.state[lastRenderedEventIndexKey]) {
this.setState({ [lastRenderedEventIndexKey]: eventIndex })
}
},

render() {
return <WrappedComponent {...this.props} />
}
})

return ContextSubscriber
}
}
6 changes: 4 additions & 2 deletions modules/Link.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { routerShape } from './PropTypes'
import { connectToContext } from './ContextUtils'
import { ContextSubscriber } from './ContextUtils'

const { bool, object, string, func, oneOfType } = React.PropTypes

Expand Down Expand Up @@ -41,6 +41,8 @@ function isEmptyObject(object) {
*/
const Link = React.createClass({

mixins: [ ContextSubscriber('router') ],

contextTypes: {
router: routerShape
},
Expand Down Expand Up @@ -122,4 +124,4 @@ const Link = React.createClass({

})

export default connectToContext(Link, 'router', object)
export default Link
18 changes: 5 additions & 13 deletions modules/RouterContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import invariant from 'invariant'
import React from 'react'

import getRouteParams from './getRouteParams'
import { createContextProvider } from './ContextUtils'
import { ContextProvider } from './ContextUtils'
import { isReactChildren } from './RouteUtils'

const { array, func, object } = React.PropTypes
const RouterContextProvider = createContextProvider('router', object.isRequired)

/**
* A <RouterContext> renders the component tree for a given router state
* and sets the history object and the current location in context.
*/
const RouterContext = React.createClass({

mixins: [ ContextProvider('router') ],

propTypes: {
router: object.isRequired,
routes: array.isRequired,
Expand Down Expand Up @@ -90,21 +91,12 @@ const RouterContext = React.createClass({
}, element)
}

const isEmpty = element === null || element === false
invariant(
isEmpty || React.isValidElement(element),
element === null || element === false || React.isValidElement(element),
'The root route must render a single element'
)

if (isEmpty) {
return element
}

return (
<RouterContextProvider>
{element}
</RouterContextProvider>
)
return element
}

})
Expand Down

0 comments on commit dcb1b51

Please sign in to comment.