Skip to content

Instantly share code, notes, and snippets.

@stewart
Last active April 2, 2017 00:12
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 stewart/80d378839ea32bfcab606d34e0abe167 to your computer and use it in GitHub Desktop.
Save stewart/80d378839ea32bfcab606d34e0abe167 to your computer and use it in GitHub Desktop.
import React from "react";
import shallowEqual from "./util/shallowEqual";
const storeShape = React.PropTypes.object.isRequired;
export function createStore(reducer, initialState) {
let state = (initialState != null) ? initialState : {};
let subscriptions = [];
const getState = () => state;
function dispatch(action) {
if (action.type == null) {
throw new Error("Actions must have a type");
}
state = reducer(state, action);
for (let fn of subscriptions) {
fn();
}
return action;
}
function subscribe(fn) {
subscriptions.push(fn);
}
function unsubscribe(fn) {
const index = subscriptions.indexOf(fn);
subscriptions.splice(index, 1);
}
dispatch({ type: "INIT_STORE" });
return { getState, dispatch, subscribe, unsubscribe };
}
export function combineReducers(reducers) {
let keys = Object.keys(reducers);
return function(state = {}, action) {
let changed = false;
let nextState = {};
keys.forEach(function(key) {
const reducer = reducers[key];
const current = state[key];
const next = reducer(current, action);
nextState[key] = next;
changed = changed || next !== current;
});
return changed ? nextState : state;
};
}
export class Provider extends React.Component {
getChildContext() {
return { store: this.props.store };
}
render() {
return React.Children.only(this.props.children);
}
}
Provider.propTypes = { store: storeShape };
Provider.childContextTypes = { store: storeShape };
Provider.displayName = "Provider";
export function connect(WrappedComponent, mapStateToProps) {
const createZipper = (store) => {
const run = (props) => {
const state = store.getState();
const nextProps = Object.assign({}, props, mapStateToProps(state));
if (!(shallowEqual(zipper.props, nextProps))) {
zipper.props = nextProps;
zipper.shouldComponentUpdate = true;
}
}
const zipper = { props: {}, shouldComponentUpdate: false, run };
return zipper;
}
class Connect extends React.Component {
constructor(props, context) {
super(props, context);
this.onStateChange = this.onStateChange.bind(this);
this.store = context.store;
this.initializeZipper();
}
componentDidMount() {
this.store.subscribe(this.onStateChange);
this.zipper.run(this.props);
if (this.zipper.shouldComponentUpdate) {
this.forceUpdate();
}
}
componentWillReceiveProps(nextProps) {
this.zipper.run(nextProps);
}
shouldComponentUpdate() {
return this.zipper.shouldComponentUpdate;
}
componentWillUnmount() {
this.store.unsubscribe(this.onStateChange);
this.store = null;
this.zipper.run = function() {};
this.zipper.shouldComponentUpdate = false;
}
initializeZipper() {
this.zipper = createZipper(this.store);
this.zipper.run(this.props);
}
onStateChange() {
this.zipper.run(this.props);
if (this.zipper.shouldComponentUpdate) {
this.setState({});
}
}
render() {
const zipper = this.zipper;
zipper.shouldComponentUpdate = false;
return (
<WrappedComponent { ...zipper.props }/>
);
}
}
Connect.contextTypes = { store: storeShape };
Connect.displayName = "Connect";
return Connect;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment