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; }