Skip to content

Instantly share code, notes, and snippets.

@robcolburn
Last active October 23, 2015 23:18
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 robcolburn/8d77e18d82b2c88f40d4 to your computer and use it in GitHub Desktop.
Save robcolburn/8d77e18d82b2c88f40d4 to your computer and use it in GitHub Desktop.
/* 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;
};
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