Skip to content

Instantly share code, notes, and snippets.

@SimonDegraeve
Last active August 29, 2015 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SimonDegraeve/fab1e3bb45a10f81a324 to your computer and use it in GitHub Desktop.
Save SimonDegraeve/fab1e3bb45a10f81a324 to your computer and use it in GitHub Desktop.
ConnectStores method for Flummox (like Reflux connect mixin)
/*
* 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;
}
}
/*
* 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);
}
}
/*
* 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)
/*
* 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
// ...
}
}
@acdlite
Copy link

acdlite commented Feb 11, 2015

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