Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active August 29, 2015 14:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattmccray/ea4ae1f4c819db2903df to your computer and use it in GitHub Desktop.
Save mattmccray/ea4ae1f4c819db2903df to your computer and use it in GitHub Desktop.
AltResolver higher order component (decorator)
import {hasComponentChanged} from 'util'
const ignoredStatics= Object.getOwnPropertyNames( class {})
.concat([ '_altResolved', 'displayName', 'type'])
export var AltResolver= Wrapped => {
// Don't wrap a wrapper
if( '_altResolved' in Wrapped) return Wrapped
class Resolved extends React.Component {
constructor( props, context) {
super( props, context)
this.state= this.resolveProps()
}
shouldComponentUpdate( nextProps, nextState) {
return hasComponentChanged( this, nextProps, nextState)
}
componentWillReceiveProps( nextProps) {
this.setState( this.resolveProps( nextProps))
}
componentDidMount() {
this.onStoreChange= this.onStoreChange.bind( this)
this.getSources().forEach( store => {
store.listen( this.onStoreChange)
})
if( Wrapped.setup) {
Wrapped.setup( this.props)
}
}
componentWillUnmount() {
this.getSources().forEach( store => {
store.unlisten( this.onStoreChange)
})
if( Wrapped.tearDown) {
Wrapped.tearDown( this.props)
}
}
onStoreChange() {
this.setState( this.resolveProps())
}
getSources() {
if(! this._sources) {
if( Wrapped.listenTo) {
let stores= Wrapped.listenTo( this.props)
this._sources= Array.isArray( stores) ? stores : [ stores ]
}
else {
this._sources= []
}
}
return this._sources
}
resolveProps( nextProps) {
if( Wrapped.resolveProps) {
let props= nextProps || this.props
return Wrapped.resolveProps( props, ...this.getSources())
}
else {
return {}
}
}
render() {
let props= this.props, state= this.state
return <Wrapped {...props} {...state} />
}
}
// Transfer statics from Wrapped to Resolved
// Question: Should resolveProps and listenTo be transferred too, or not?
Object.getOwnPropertyNames( Wrapped).forEach( key => {
if( ignoredStatics.indexOf( key) < 0) {
Resolved[ key]= Wrapped[ key]
}
})
Resolved._altResolved= true
return Resolved
}
import {AltResolver} from './AltResolver'
export class UserChipWidget extends React.Component {
render() {
if( this.props.user) {
this.renderUser()
}
else {
this.renderUserLoading()
}
}
renderUser() {
return <div>Hello, my name is { this.props.user.firstName}</div>
}
renderUserLoading() {
return <div>One moment...</div>
}
// Define prop resolution in statics
static listenTo() {
return UserStore
}
static resolveProps( props) {
return {
user: UserStore.get( props.userId)
}
}
}
export var UserChip= AltResolver( UserChipWidget)
// Then, elsewhere, use it like this:
<UserChip userId={ id}/>
@mattmccray
Copy link
Author

Updated to be safer (won't wrap the same component more than once) and to transfer statics from the child component to the container.

@mattmccray
Copy link
Author

The hasComponentChanged function is just like PureRenderMixin's:

import {shallowEqual} from './shallowEqual'

export function hasComponentChanged( comp, nextProps, nextState) {
  return !shallowEqual( comp.props, nextProps) ||
         !shallowEqual( comp.state, nextState)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment