Skip to content

Instantly share code, notes, and snippets.

@roman01la
Last active February 10, 2016 00:51
Show Gist options
  • Save roman01la/d0921af89f5e74bd727f to your computer and use it in GitHub Desktop.
Save roman01la/d0921af89f5e74bd727f to your computer and use it in GitHub Desktop.
const createStore = (initialState, reducer) => {
let state = {};
let listeners = [];
const getState = () => state;
const setState = (nextState) => {
state = nextState;
return state;
};
const subscribe = (fn) => {
listeners.push(fn);
return (fn) => {
listeners = listeners.filter((l) => l !== fn);
};
};
const notify = (state) => listeners.forEach((fn) => fn(state));
const dispatch = (action) => notify(setState(reducer(getState(), action)));
setState(initialState);
return {
subscribe,
dispatch,
getState
};
};
const createReducer = (reducers) => (state, action) => reducers[action.type] ? reducers[action.type](state, action) : state;
const composeReducers = (x, ...xs) => (state, action) => xs.reduce((nextState, reducer) => reducer(nextState, action), x(state, action));
function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
const hasOwn = Object.prototype.hasOwnProperty;
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
const createConnector = (store) => (Component, mapper) => {
const getNextState = (s) => {
return Object.keys(mapper).reduce((ns, m) => {
ns[m] = mapper[m](s);
return ns;
}, {});
};
return class extends React.Component {
constructor() {
super();
this.state = { state: getNextState(store.getState()) };
this.onStateChange = (s) => this.setState({ state: getNextState(s) });
}
componentDidMount() {
this.unsubscribe = store.subscribe(this.onStateChange);
}
componentWillUnmount() {
this.unsubscribe(this.onStateChange);
}
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(nextProps, this.props) || !shallowEqual(nextState.state, this.state.state);
}
render() {
return <Component {...this.state.state} {...this.props} />;
}
}
};
const countReducer = createReducer({
'INCREMENT': (state, action) => {
return { ...state, count: state.count + 1 };
},
'DECREMENT': (state, action) => {
return { ...state, count: state.count - 1 };
}
});
const labelReducer = createReducer({
'CHANGE_LABEL': (state, action) => {
return { ...state, label: action.label };
}
});
const store = createStore({ count: 0, label: '' }, composeReducers(countReducer, labelReducer));
const connect = createConnector(store);
const Button = ({ children, ...props }) => <button {...props}>{children}</button>;
const Label = ({ children }) => <span>{children}</span>;
const App = ({ count }) => (
<div>
<Button onClick={() => store.dispatch({ type: 'DECREMENT' })}>-</Button>
<Label>{count}</Label>
<Button onClick={() => store.dispatch({ type: 'INCREMENT' })}>+</Button>
</div>
);
const Root = connect(App, {
count: (state) => state.count
});
ReactDOM.render(<Root />, document.body);
store.dispatch({ type: 'CHANGE_LABEL', label: 'Yo!' });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment