Last active
October 23, 2015 23:18
-
-
Save robcolburn/8d77e18d82b2c88f40d4 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* eslint valid-jsdoc: 0 */ | |
const React = require('react'); | |
const shallowEqual = require('react/lib/shallowEqual'); | |
const find = require('lodash/collection/find'); | |
const TheStore = require('../TheStore'); | |
/** | |
* Determine if an object has an array of keys. | |
* | |
* @param {object} object | |
* Object to check. | |
* @param {string[]} keys | |
* Array of keys to check. | |
* | |
* @return {boolean} | |
* If object has all of the keys. | |
*/ | |
function hasKeys(object, keys) { | |
let i = keys.length; | |
while (i--) { | |
if (!object.hasOwnProperty([keys[i]])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Creates a Container component to wrap a given compoent. | |
* | |
* @param {ReactComponent} Component | |
* The Component to wrap with a data layer. | |
* @param {object} options | |
* Key-value options | |
* @param {string} options.name | |
* Name to call this (key to use in TheStore) | |
* @param {ReactComponent} options.Loading | |
* Component to render while still loading. | |
* If undefined, uses Component | |
* @param {ReactComponent} options.Failure | |
* Component to render while in a failure state. | |
* If undefined, uses Component | |
* @param {function} options.load | |
* Function that, given params, promises to load data for this container. | |
* @param {function} options.isStateAccurate | |
* Optional. Given (props, state), returns TRUE if state has accurate data. | |
* @param {string[]} options.storeKeys | |
* An array of keys to validate if state from the Store is in valid. | |
* | |
* @return {ReactComponent} | |
* Wrapping Component w/ Data. | |
*/ | |
module.exports = function createContainer(Component, options) { | |
const {name, Loading, Failure, load, storeKeys} = options; | |
const Container = React.createClass({ | |
displayName: name, | |
// Pull Initial State from store (maybe it's already loaded) | |
getInitialState() { | |
return TheStore.getState(); | |
}, | |
componentWillReceiveDispatch() { | |
this.setState(TheStore.getState()); | |
}, | |
// Pick up changes to the store | |
componentDidMount() { | |
this.unsubscribe = TheStore.subscribe(this.componentWillReceiveDispatch); | |
}, | |
componentWillUnmount() { | |
this.unsubscribe(); | |
}, | |
// Load & Pre-load | |
componentWillMount() { | |
// Client: Check if load if needed | |
if (typeof window !== "undefined" | |
&& !this.isFailure() | |
&& this.isLoading() | |
) { | |
Container.load(this.props).then(TheStore.set); | |
} | |
}, | |
// React Router is bringing this component in. | |
componentWillReceiveProps(nextProps) { | |
if (!shallowEqual(this.props, nextProps)) { | |
Container.load(nextProps).then(TheStore.set); | |
} | |
}, | |
/** | |
* Determine if a key, we are concerned with, is an error. | |
* | |
* @param {*} value | |
* Value in the store to check. | |
* @param {string} key | |
* Key of the Store to check. | |
* @return {boolean} | |
* True, if we are concerned with this key, and the value is an error. | |
*/ | |
isInvalidStoreValue(value, key) { | |
return storeKeys.indexOf(key) >= 0 && (value instanceof Error || value.error); | |
}, | |
/** | |
* Determine if Container has failed for this component. | |
* | |
* @return {boolean} | |
* True if data has, in fact, failed. | |
*/ | |
isFailure() { | |
if (storeKeys && hasKeys(this.state, storeKeys) && find(this.state, this.isInvalidStoreValue)) { | |
return true; | |
} | |
return false; | |
}, | |
/** | |
* Determine if Container has data for this component. | |
* | |
* @return {boolean} | |
* True if data is, in fact, loaded. | |
*/ | |
isLoading() { | |
if (storeKeys && !hasKeys(this.state, storeKeys)) { | |
return true; | |
} | |
if (options.isStateAccurate) { | |
return !options.isStateAccurate(this.props, this.state); | |
} | |
return false; | |
}, | |
render() { | |
const component = ( | |
(this.isFailure() && Failure) | |
|| (this.isLoading() && Loading) | |
|| Component | |
); | |
return component && React.createElement(component, {...this.props, ...this.state}); | |
} | |
}); | |
Container.load = props => load(props); | |
return Container; | |
}; |
This file contains hidden or 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
const {createStore} = require('redux'); | |
const mapValues = require('lodash/object/mapValues'); | |
const get = require('lodash/object/get'); | |
const initialState = (typeof window !== "undefined" && window.PRELOAD) || {}; | |
const reducers = {}; | |
function serializeable(value) { | |
return ( | |
value instanceof Error ? {error: value.stack} | |
: value | |
); | |
} | |
const TheStore = createStore((state = initialState, action) => | |
reducers[action.type] ? reducers[action.type](state, action) : state | |
); | |
TheStore.serialize = () => 'PRELOAD=' + JSON.stringify(mapValues(TheStore.getState(), serializeable)); | |
// Reduce the REPLACE Action | |
reducers.REPLACE = (state, action) => action.state; | |
TheStore.replace = (state) => TheStore.dispatch({type: 'REPLACE', state}); | |
// Reduce the SET Action | |
reducers.SET = (state, {key, value}) => ( | |
typeof key === "object" ? {...state, ...key} : {...state, [key]: value} | |
); | |
TheStore.set = (key, value) => TheStore.dispatch({type: 'SET', key, value}); | |
TheStore.get = key => get(TheStore.getState(), key, null); | |
module.exports = TheStore; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment