Last active
August 29, 2015 14:16
-
-
Save mattmccray/5f3a8cd7935404830a63 to your computer and use it in GitHub Desktop.
Experimental controller component for use with alt stores.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {alt} from 'data/alt' // Alt instance | |
let {PropTypes:Types, Component}= React // or require( 'react') | |
export class Controller extends Component { | |
// With babel 'playground' enabled, you can do this: | |
// static propTypes= { | |
// source: Types.any.isRequired, | |
// provide: React.PropTypes.oneOfType([ | |
// React.PropTypes.object, | |
// React.PropTypes.func | |
// ]), | |
// onInit: Types.func | |
// } | |
constructor(props, context) { | |
super(props, context) | |
this.props= props | |
this.state= this.getProvidedData() | |
} | |
shouldComponentUpdate() { | |
return false | |
} | |
componentDidMount() { | |
this.getSources().forEach(( store)=>{ | |
store.listen( this.updateState) | |
}) | |
// DEPRECATE? Might be useful to trigger a fetch action... | |
if( this.props.onInit) { | |
this.props.onInit( ...this.getSources()) | |
} | |
} | |
componentWillUnmount() { | |
this.getSources().forEach(( store)=>{ | |
store.unlisten( this.updateState) | |
}) | |
} | |
updateState() { | |
this.setState( this.getProvidedData()) | |
} | |
getSources() { | |
if(! this._sources) { | |
if( Array.isArray( this.props.source)) { | |
this._sources= this.props.source.map( this.expandSource) | |
} | |
else { | |
this._sources= [ this.expandSource( this.props.source)] | |
} | |
} | |
return this._sources | |
} | |
expandSource( source) { | |
if( typeof( source) === 'string') { | |
return alt.getStore( source) | |
} | |
else { | |
return source | |
} | |
} | |
getProvidedData() { | |
let providerType= typeof( this.props.provide) | |
if( providerType === 'object') { | |
return this.props.provide | |
} | |
else if( providerType === 'function') { | |
return this.props.provide( ...this.getSources()) | |
} | |
else { | |
let states= this.getSources().map( store => store.getState()) | |
return _extend({}, ...states) | |
} | |
} | |
wrapChild( child) { | |
let { children, source, provide, onInit, ...props }= this.props | |
return React.cloneElement( | |
child, | |
_extend( | |
{alt}, this.state, props | |
) | |
) | |
} | |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | |
render() { | |
let { children, source, provide, ...props }= this.props | |
if(! children) return null | |
if( Array.isArray( children)) { | |
return ( | |
<span {...props}> | |
{ React.Children.map( children, this.wrapChild.bind( this))} | |
</span> | |
) | |
} | |
else { | |
return ( | |
<span {...props}> | |
{ this.wrapChild( children)} | |
</span> | |
) | |
} | |
} | |
} | |
Controller.propTypes= { | |
source: Types.any.isRequired, | |
provide: React.PropTypes.oneOfType([ | |
React.PropTypes.object, | |
React.PropTypes.func | |
]), | |
onInit: Types.func | |
} | |
// Inline helper so underscore/lodash isn't needed | |
function _extend( target) { | |
Array.prototype.slice.call(arguments, 1) | |
.forEach(( source) => { | |
if( source) { | |
for(let prop in source) { | |
target[ prop]= source[ prop] | |
} | |
} | |
}) | |
return target | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Based on ideas/code from: | |
https://github.com/acdlite/flummox/blob/master/docs/why-flux-component-is-better-than-flux-mixin.md | |
Assuming the `_id` variable below is, perhaps, from props/router/other. | |
Example Usage: | |
<Controller key={ _id} source={ ProjectStore} provide={ () => { ProjectStore.getState() }}> | |
<ProjectList/> | |
</Controller> | |
Alternate Usage: | |
<Controller key={ _id} source={[ BlogStore, TagStore]} provide={() => { | |
return { | |
post: BlogStore.get( _id), | |
tags: TagsStore.getFor( _id) | |
} | |
}}> | |
<BlogPost/> | |
</Controller> | |
Nested Controllers. While technically possible, probably not a great idea? | |
<Controller source={ BlogStore} provide={( Blog) => { | |
return { post: Blog.get( _id) } | |
}}> | |
<Controller source={ TagStore} provide={( Tags) => { | |
return { tags:Tags.getFor( _id) } | |
}}> | |
<BlogPost/> | |
</Controller> | |
</Controller> | |
*/ | |
import {alt} from 'data/alt' // Alt instance | |
let Types= React.PropTypes | |
export let Controller= React.createClass({ | |
propTypes: { | |
source: Types.any.isRequired, | |
provide: React.PropTypes.oneOfType([ | |
React.PropTypes.object, | |
React.PropTypes.func | |
]), | |
onInit: Types.func | |
}, | |
shouldComponentUpdate() { | |
return false | |
}, | |
getInitialState() { | |
return this.getProvidedData() | |
}, | |
componentDidMount() { | |
this.getSources().forEach(( store)=>{ | |
store.listen( this.updateState) | |
}) | |
// DEPRECATE? Might be useful to trigger a fetch action... | |
if( this.props.onInit) { | |
this.props.onInit( ...this.getSources()) | |
} | |
}, | |
componentWillUnmount() { | |
this.getSources().forEach(( store)=>{ | |
store.unlisten( this.updateState) | |
}) | |
}, | |
updateState() { | |
this.setState( this.getProvidedData()) | |
}, | |
getSources() { | |
if(! this._sources) { | |
if( Array.isArray( this.props.source)) { | |
this._sources= this.props.source.map( this.expandSource) | |
} | |
else { | |
this._sources= [ this.expandSource( this.props.source)] | |
} | |
} | |
return this._sources | |
}, | |
expandSource( source) { | |
if( typeof( source) === 'string') { | |
return alt.getStore( source) | |
} | |
else { | |
return source | |
} | |
}, | |
getProvidedData() { | |
let providerType= typeof( this.props.provide) | |
if( providerType === 'object') { | |
return this.props.provide | |
} | |
else if( providerType === 'function') { | |
return this.props.provide( ...this.getSources()) | |
} | |
else { | |
let states= this.getSources().map( store => store.getState()) | |
return _extend({}, ...states) | |
} | |
}, | |
wrapChild( child) { | |
let { children, source, provide, onInit, ...props }= this.props | |
// For 0.13 | |
// return React.cloneElement( | |
// child, | |
// _extend( | |
// {alt}, this.state, props | |
// ) | |
// ) | |
// For 0.12 | |
return React.addons.cloneWithProps( | |
child, | |
_extend( | |
{alt}, this.state, props | |
) | |
) | |
}, | |
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | |
render() { | |
let { children, source, provide, ...props }= this.props | |
if(! children) return null | |
if( Array.isArray( children)) { | |
return ( | |
<span {...props}> | |
{ React.Children.map( children, this.wrapChild)} | |
</span> | |
) | |
} | |
else { | |
return ( | |
<span {...props}> | |
{ this.wrapChild( children)} | |
</span> | |
) | |
} | |
} | |
}) | |
// Inline helper so underscore/lodash isn't needed | |
function _extend( target) { | |
Array.prototype.slice.call(arguments, 1) | |
.forEach(( source) => { | |
if( source) { | |
for(let prop in source) { | |
target[ prop]= source[ prop] | |
} | |
} | |
}) | |
return target | |
} |
<Controller source={Store | StoreArray} context={ Object | Function }/>
Instead of context
maybe childProps
? That's at least explicit.
Any suggestions are welcome.
Truth be told, I'm not even sold on Controller
as the component name.
Maybe provide
Updated React 0.12 version to not use mixins itself.
Added a React 0.13 ready ES6 class version.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Known issues:
context
is a bad name. 😄