Skip to content

Instantly share code, notes, and snippets.

@mostr
Last active March 23, 2017 08:08
Show Gist options
  • Save mostr/00222aae6f645a53dddc5a228be59d0d to your computer and use it in GitHub Desktop.
Save mostr/00222aae6f645a53dddc5a228be59d0d to your computer and use it in GitHub Desktop.
State mgmt
export function inc() {
return (getState, mergeState) => {
const currentCounter = getState().counter;
if(currentCounter === 5) {
console.log('5 reached, cannot inc')
return;
}
return mergeState({counter: currentCounter + 1});
}
}
export function incAsync() {
return (getState, mergeState) => {
const currentCounter = getState().counter;
if(currentCounter === 5) {
console.log('5 reached, cannot inc async')
return;
}
setTimeout(() => mergeState({counter: getState().counter + 1}), 1000);
}
}
import React from 'react';
import {connect} from './statemgmt';
import * as actions from './actions';
const App = () => <Counter/>
export default App;
function _Counter(props) {
return (
<div>
<h2>Current value: {props.current}</h2>
<hr/>
<button onClick={props.inc}>Inc</button>
<button onClick={props.incAsync}>Inc async</button>
</div>
)
}
function storeSlice(state) {
return {
current: state.counter
}
}
const Counter = connect(_Counter, storeSlice, actions)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {createStore,Provider} from './statemgmt';
const store = createStore({
counter: 0
});
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
import React from 'react';
export function createStore(initialState) {
let state = initialState || Object.create(null);
let subscribedComponents = [];
return {
subscribe(component) {
subscribedComponents.push(component);
},
unsubscribe(component) {
subscribedComponents = subscribedComponents.filter(c => c !== component);
},
mergeState(newState) {
state = {...state, ...newState};
subscribedComponents.forEach(c => c.forceUpdate());
},
getState() {
return state;
}
}
}
export class Provider extends React.Component {
static childContextTypes = {
store: React.PropTypes.object
};
getChildContext() {
return {store: this.props.store}
}
render() {
return this.props.children;
}
}
export function connect(Component, storeProjection, actions) {
storeProjection = storeProjection || (() => {});
if(storeProjection != null && typeof storeProjection !== 'function') {
throw new TypeError('Store projection must either be null or a function f(state, ownProps) => object');
}
actions = actions || {};
let nonFunctionActions = Object.keys(actions || {}).filter(key => typeof actions[key] !== 'function');
if(nonFunctionActions.length) {
throw new TypeError(`The following actions are not functions: ${nonFunctionActions.join(', ')}`);
}
function augmentSingleAction(store, fn) {
return (...args) => {
const outer = fn(...args);
if(typeof outer !== 'function') {
throw new TypeError('Action must return a function f(getState, mergeState)');
}
return outer(store.getState, store.mergeState);
}
}
function augmentActions(actions, store) {
return Object.keys(actions).reduce((result, key) => {
return {...result, [key]: augmentSingleAction(store, actions[key])};
}, {});
}
return class ConnectedComponent extends React.Component {
static contextTypes = {
store: React.PropTypes.object
};
constructor(props, context) {
super(props, context);
this.store = context.store;
this.store.subscribe(this);
this.augmentedActions = augmentActions(actions, this.store);
}
componentWillUnmount() {
this.store.unsubscribe(this);
}
render() {
return <Component {...this.props} {...storeProjection(this.store.getState(), this.props)} {...this.augmentedActions}/>
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment