Skip to content

Instantly share code, notes, and snippets.

@ali-master
Forked from developit/unistore.js
Created October 22, 2017 07:19
Show Gist options
  • Save ali-master/d173d8bf39daf2221cf75e5986ad86a6 to your computer and use it in GitHub Desktop.
Save ali-master/d173d8bf39daf2221cf75e5986ad86a6 to your computer and use it in GitHub Desktop.

Revisions

  1. @developit developit revised this gist Feb 21, 2017. No changes.
  2. @developit developit created this gist Feb 21, 2017.
    102 changes: 102 additions & 0 deletions unistore.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    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();
    * <Provider store={store}><App /></Provider>
    */
    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 }) => <div /> )
    * @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;
    }