Skip to content

Instantly share code, notes, and snippets.

@jorgeborges
Forked from developit/unistore.js
Created October 5, 2017 08:22
Show Gist options
  • Save jorgeborges/843e4e735fe8e667f6fff68f4b0f624b to your computer and use it in GitHub Desktop.
Save jorgeborges/843e4e735fe8e667f6fff68f4b0f624b to your computer and use it in GitHub Desktop.
dead simple centralized state container ("store"), with preact bindings.
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment