Update: the newer & better version of this is published: https://github.com/developit/unistore
December 7, 2017 ยท View on GitHub
import { h, Component } from 'preact';
/** Creates a new store, which is a tiny evented state container.
- @example
- let store = createStore();
- store.subscribe( state => console.log(state) );
- store.setState({ a: 'b' }); // logs { a: 'b' }
- store.setState({ c: 'd' }); // logs { c: 'd' } */ export default function createStore(state={}) { let listeners = [];
return {
setState(update) {
state = { ...state, ...update };
listeners.forEach( f => f(state) );
},
subscribe(f) {
listeners.push(f);
},
unsubscribe(f) {
let i = listeners.indexOf(f);
listeners.splice(i, !!~i);
},
getState() {
return state;
}
};
}
/** Provides its props into the tree as context.
- @example
- let store = createStore();
*/ export class Provider extends Component { getChildContext() { let { children, ...context } = this.props; return context; } render({ children }) { return children[0]; } }
/** Wire a component up to the store. Passes state as props, re-renders on change.
- @param {Function|Array|String} mapStateToProps A function (or any
select()argument) mapping of store state to prop values. - @example
- const Foo = connect('foo,bar')( ({ foo, bar }) => )
- @example
- @connect( state => ({ foo: state.foo, bar: state.bar }) )
- export class Foo { render({ foo, bar }) { } } */ export function connect(mapToProps) { if (typeof mapToProps!=='function') mapToProps = select(mapToProps); return Child => class Wrapper extends Component { state = this.getProps(); update = () => { let mapped = this.getProps(); if (!shallowEqual(mapped, this.state)) { this.setState(mapped); } }; getProps() { let state = this.context.store && this.context.store.getState() || {}; return mapToProps(state); } componentWillMount() { this.context.store.subscribe(this.update); } componentWillUnmount() { this.context.store.unsubscribe(this.update); } render(props, state, context) { return <Child store={context.store} {...props} {...state} />; } }; }
/** select('foo,bar') creates a function of the form: ({ foo, bar }) => ({ foo, bar }) */ export function select(properties) { if (typeof properties==='string') properties = properties.split(','); return state => { let selected = {}; for (let i=0; i<properties.length; i++) { selected[properties[i]] = state[properties[i]]; } return selected; }; }
/** Returns a boolean indicating if all keys and values match between two objects. */ function shallowEqual(a, b) { for (let i in a) if (a[i]!==b[i]) return false; for (let i in b) if (!(i in a)) return false; return true; }