Last active
August 29, 2015 14:15
-
-
Save SimonDegraeve/fab1e3bb45a10f81a324 to your computer and use it in GitHub Desktop.
ConnectStores method for Flummox (like Reflux connect mixin)
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 libraries | |
*/ | |
import React from 'react'; | |
/* | |
* Define helpers | |
*/ | |
function capitalize(str) { | |
return (str.charAt(0).toUpperCase() + str.slice(1)).replace(/\s/g,''); | |
} | |
/* | |
* Define and export App component | |
* | |
* This class get a Flux instance as 'flux' prop | |
* and expose it as 'flux' context to his children | |
* | |
* We start with a prop to avoid React warning about | |
* 'owner-based and parent-based contexts differ' | |
* See https://gist.github.com/jsfb/0eb6e61f300a8c1b2ce7 | |
*/ | |
export default class App extends React.Component { | |
// Flux/context specific | |
static get propTypes() { | |
return { | |
flux: React.PropTypes.object.isRequired | |
} | |
} | |
static get childContextTypes() { | |
return { | |
flux: React.PropTypes.object.isRequired | |
} | |
} | |
getChildContext() { | |
return { flux: this.props.flux }; | |
} | |
// Component specific | |
constructor(props, context) { | |
super(props, context); | |
// Bind instance methods | |
this.connectStores = this.connectStores.bind(this); | |
// Connect store state with local state | |
// | |
// - If connectStores() get store instance: | |
// default listener is `onMyStoreChange` (construct from `on` + store displayName + `Change`) | |
// if `onMyStoreChange` does not exist then default is `function() { this.setState(myStore.state); };` | |
// - Else connectStores() expect spec object: | |
// ex: { store: myStore, key: 'myCustomKey', callback: this.myCustomCallback } | |
// NB: if callback not provided then default to `on` + key + `Change` (ex: `OnMyCustomKeyChange`) | |
// if key not provided then default to store displayName | |
// | |
// State is automatically merged from store state | |
// Stores can be accessed in this.stores.myStore (default) or this.stores.myCustomKey if using object spec | |
var myStore = props.flux.getStore('myStore'); | |
this.connectStores([ | |
myStore | |
// some other stores | |
// ... | |
]); | |
// Or | |
// this.connectStores([ | |
// { store: myStore, key: 'myStore', callback: this.onMyStoreChange } | |
// // some other stores | |
// // ... | |
// ]); | |
} | |
connectStores(storesSpec = []) { | |
// Check if method already called | |
if (this._hasConnectedStores) { | |
throw Error('`connectStores()` must be called only once per component.') | |
} | |
// Set boolean to track method call | |
this._hasConnectedStores = true; | |
// Set easy access to stores in component instance | |
this.stores = {}; | |
// Normalize stores specs | |
this._storesSpec = storesSpec.map(storeSpec => { | |
// Normalize spec if store instance provided | |
if (typeof storeSpec._token !== 'undefined') { | |
storeSpec = { store: storeSpec }; | |
} | |
// Set default key if not provided | |
if (typeof storeSpec.key === 'undefined') { | |
storeSpec.key = storeSpec.store.constructor.displayName; | |
} | |
// Set default callback if not provided | |
if (typeof storeSpec.callback === 'undefined') { | |
var callbackName = 'on' + capitalize(storeSpec.key) + 'Change'; | |
if (typeof this[callbackName] === 'undefined') { | |
this[callbackName] = function() { | |
this.setState(storeSpec.store.state); | |
}.bind(this); | |
} | |
storeSpec.callback = this[callbackName].bind(this); | |
} | |
// Add store to the instance list of stores | |
this.stores[storeSpec.key] = storeSpec.store; | |
// Merge store state with instance state | |
this.state = Object.assign({}, this.state, storeSpec.store.state); | |
// Return normalize spec | |
return storeSpec; | |
}); | |
// Add/remove listeners in component lifecycle methods | |
var _componentDidMount = this.componentDidMount || function() {}; | |
this.componentDidMount = function() { | |
this._storesSpec.forEach(storeSpec => { | |
storeSpec.store.addListener('change', storeSpec.callback); | |
}); | |
_componentDidMount.call(this); | |
}; | |
var _componentWillUnmount = this.componentWillUnmount || function() {}; | |
this.componentWillUnmount = function() { | |
this._storesSpec.forEach(storeSpec => { | |
storeSpec.store.removeListener('change', storeSpec.callback); | |
}); | |
_componentWillUnmount.call(this); | |
}; | |
} | |
render() { | |
return null; | |
} | |
} |
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 libraries | |
*/ | |
import { Flux } from 'flummox'; | |
/* | |
* Import stores/actions | |
*/ | |
import MyStore from './MyStore'; | |
/* | |
* Define and export AppFlux flux | |
*/ | |
export default class AppFlux extends Flux { | |
constructor() { | |
super(); | |
// Create some actions | |
// ... | |
// Create some stores | |
// NB: Would be nice to have shortcut if first argument is not a string then | |
// use the `displayName` static prop | |
// ex: | |
// this.createStore(ContentStore, this); | |
this.createStore(ContentStore.displayName, ContentStore, this); | |
} | |
} |
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 libraries | |
*/ | |
import '6to5/register'; | |
import Flux from './AppFlux'; | |
import React from 'react'; | |
/* | |
* Import components | |
*/ | |
import App from './AppComponent'; | |
/* | |
* Render application | |
*/ | |
var flux = new Flux(); | |
React.render(<App flux={flux}/>, document.body) |
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 libraries | |
*/ | |
import { Store } from 'flummox'; | |
/* | |
* Define and export MyStore store | |
*/ | |
export default class MyStore extends Store { | |
// NB: Would be nice to have this native | |
// | |
// Rely on this static prop because cannot trust | |
// constructor name if script mangled (ex: uglifyjs) | |
// | |
// Or | |
// MyStore.displayName = 'myStore'; | |
static get displayName() { | |
return 'myStore'; | |
} | |
constructor(flux) { | |
super(); | |
// Register some actions | |
// ... | |
// Set some state | |
// ... | |
} | |
} |
Oh I see, it's not doing what I thought it was on first glance. Hmm.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What's the advantage of the static
displayName
property? Instead of defining the name increateStore
, you're defining it some place else... Is there really any benefit?The
connectStores
implementation is interesting. Kinda similar to what I'm working on. I don't know how I feel about the default listener behavior, though. What's an example use case for listener callbacks if state is automatically being merged, anyway?