Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active August 29, 2015 14:16
Show Gist options
  • Save mattmccray/5f3a8cd7935404830a63 to your computer and use it in GitHub Desktop.
Save mattmccray/5f3a8cd7935404830a63 to your computer and use it in GitHub Desktop.
Experimental controller component for use with alt stores.
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
}
/*
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
}
@mattmccray
Copy link
Author

<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.

@mattmccray
Copy link
Author

Maybe provide

@mattmccray
Copy link
Author

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