Last active
July 11, 2016 23:33
-
-
Save mcrowe/f64f51da4e0a8acb1a9ae158dcde6f4d to your computer and use it in GitHub Desktop.
Simple flux example
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 React from 'react' | |
import { createConnector } from '../lib/connector' | |
import DB from './db' | |
import env from '../config/environment' | |
import Logger from './logger' | |
const DebugError = ({error, componentDisplayName}) => | |
<div cl="connect-error-debug"> | |
<div cl="connect-error-message">{error.message}</div> | |
{componentDisplayName && | |
<div cl="connect-error-display-name">connecting props for {componentDisplayName}</div> | |
} | |
<div cl="connect-error-stack">{error.stack}</div> | |
</div> | |
const ProductionError = () => | |
<div cl="connect-error-production"> | |
<h2>Sorry, something went wrong.</h2> | |
<p><a href="/">Go back to safety</a></p> | |
</div> | |
const stores = [DB] | |
function renderError(error, componentDisplayName) { | |
if (env.debug) { | |
return <DebugError error={error} componentDisplayName={componentDisplayName} /> | |
} else { | |
return <ProductionError /> | |
} | |
} | |
function handleError(error, componentDisplayName) { | |
Logger.error(`Error connecting props for ${componentDisplayName}. ${error.message}`) | |
} | |
const connect = createConnector({stores, renderError, handleError}) | |
export default connect |
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 React from 'react' | |
import { subscribeAll } from './store' | |
import assert from './assert' | |
function componentName(component) { | |
return component.displayName || component.name | |
} | |
/** | |
* @callback renderErrorCallback | |
* @param {Error} error - The error. | |
* @param {string} componentDisplayName - The display name of the component. | |
* @return A React component to render. | |
*/ | |
/** | |
* @callback handleErrorCallback | |
* @param {Error} error - The error. | |
* @param {string} componentDisplayName - The display name of the component. | |
*/ | |
/** | |
* Create a connector customized for your app's subscriptions. | |
* @param {Object[]} stores - An array of stores to subscribe to. | |
* The component will be re-rendered when any of these stores is updated. | |
* @param {renderErrorCallback} renderError - Render errors when resolving props. | |
* @param {handleErrorCallback=} handleError - Handle errors when resolving props. | |
*/ | |
export const createConnector = ({stores, renderError, handleError}) => buildProps => component => { | |
assert(_.isArray(stores), "you must provide an array of 'stores' to subscribe to") | |
assert(_.isFunction(renderError), "you must provide a function 'renderError' to render errors when resolving props") | |
handleError = handleError || function() {} | |
return React.createClass({ | |
getInitialState() { | |
return this.buildState() | |
}, | |
componentWillReceiveProps(nextProps) { | |
this.setState(this.buildState()) | |
}, | |
componentWillMount() { | |
this.unsubscribe = subscribeAll(stores, () => | |
this.setState(this.buildState()) | |
) | |
}, | |
componentWillUnmount() { | |
this.unsubscribe() | |
}, | |
buildState() { | |
try { | |
return { | |
props: buildProps(this.props), | |
error: null | |
} | |
} catch(error) { | |
handleError(error, componentName(component)) | |
return { | |
props: null, | |
error: error | |
} | |
} | |
}, | |
render() { | |
if (this.state.error) { | |
const displayName = componentName(component) | |
return renderError(this.state.error, displayName) | |
} else { | |
return React.createElement(component, this.state.props, this.props.children) | |
} | |
} | |
}) | |
} |
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
// Stores are simple values that can be subscribed to, nothing | |
// more complicated than that. | |
// You can have multiple stores representing different objects. | |
const countStore = createStore(1) | |
countStore.subscribe(value => console.log('count updated with', value)) | |
countStore.set(2) | |
// "connect" creates a higher-order component which subscribes to changes | |
// to all the stores you care about and re-renders the given component as needed. | |
// You could also subscribe manually to the stores in componentDidMount, but | |
// this avoids duplicating that logic. | |
const App = React.createClass({...}) | |
connect(props => { | |
// Return the props to pass to the "App" component. This can access | |
// stores directly, or through convenience methods on services. | |
// It will get re-run when the stores change. This is wasteful in | |
// some cases, but that doesn't actually matter much in the real world. | |
return { | |
count: countStore.get() | |
} | |
})(App) | |
// Write your own service layer to make the store easier to work with. | |
// These are simple functions that set/get the value of stores directly. | |
// Actions and forced synchronous updates are beautiful theoretically, | |
// and can be really effective sometimes, but they definitely have a cost | |
// in Javascript. For many apps, this is much much simpler. | |
let Counter = {} | |
Counter.get = () => countStore.get() | |
Counter.set = (val) => countStore.set(val) | |
Counter.increment = () => Counter.set(Counter.get() + 1) | |
// ... | |
// Do you *really* need actions, action creators, reducers, files filled with constants, etc? | |
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
function createStore(initialValue) { | |
let subscriptions = [] | |
let val = initialValue | |
function clone() { | |
return _.cloneDeep(val) | |
} | |
function broadcast() { | |
subscriptions.forEach(fn => fn(clone())) | |
} | |
return { | |
get() { | |
return clone() | |
}, | |
set(newValue) { | |
val = _.cloneDeep(newValue) | |
broadcast() | |
}, | |
update(fn) { | |
const newValue = fn(clone()) | |
this.set(newValue) | |
}, | |
subscribe(fn) { | |
subscriptions.push(fn) | |
// Call the subscription so that it gets executed with the | |
// initial value. | |
fn(clone(), INITIALIZE_LABEL) | |
// Return an unsubscribe function. | |
return function() { | |
const idx = subscriptions.indexOf(fn) | |
if (idx >= 0) { | |
subscriptions.splice(idx, 1) | |
} | |
} | |
} | |
} | |
} | |
export function subscribeAll(stores, cb) { | |
const unsubscribes = stores.map(store => | |
store.subscribe(() => | |
cb( stores.map(s => s.get()) ) | |
) | |
) | |
// Return a function to unsubscribe to all stores. | |
return function() { | |
unsubscribes.forEach(fn => fn()) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment