Skip to content

Instantly share code, notes, and snippets.

@developit
Last active July 25, 2023 12:54
Show Gist options
  • Save developit/ecd64416d955874c3426d4582045533a to your computer and use it in GitHub Desktop.
Save developit/ecd64416d955874c3426d4582045533a to your computer and use it in GitHub Desktop.

Unistore Hooks for Preact

Experimental hooks-based bindings for Unistore. Available on npm:

npm i unistore-hooks

Note: this should also work with React, just alias "preact" and "preact/hooks" to "react" in your bundler.

Usage

import { h, render } from 'preact';
import createStore from 'unistore';
import { Provider, useStoreState, useActions } from 'unistore-hooks';

const store = createStore({
  count: 0
});

const ACTIONS = {
  increment({ count }) {
    return { count: count + 1 };
  },
  decrement({ count }) {
    return { count: count - 1 };
  },
  setCount({ }, newCount) {
    return { count: newCount };
  }
};

function App(props) {
  // get state values from the store:
  const { count } = useStoreState('count');
  
  // bind + return all actions:
  const { increment, decrement } = useActions(ACTIONS);

  // or create custom bound actions:
  const { reset, add10 } = useActions(ACTIONS, actions => ({
    reset: () => actions.setCount(0),
    add10: () => actions.increment(10)
  }));

  // or declare parameterized actions:
  const max = props.max || 999;
  const { setToMax } = useActions(ACTIONS, actions => ({
    setToMax: [actions.setCount, max] // equivalent to `actions.setCount(max)`
  }), [max]);
  
  return (
    <div>
      <button onClick={decrement}>-1</button>
      Current value: {count}
      <button onClick={increment}>+1</button>
      <button onClick={reset}>Reset</button>
      <button onClick={setToMax}>Max</button>
    </div>
  );
}

render(
  <Provider value={store}>
    <App />
  </Provider>,
  document.body
);
node_modules
package-lock.json
dist
import { createContext } from 'preact';
import { useContext, useMemo, useCallback, useReducer, useEffect } from 'preact/hooks';
// npm.im/dlv
function dlv(t,e,l,n,r){for(e=e.split?e.split('.'):e,n=0;n<e.length;n++)t=t?t[e[n]]:r;return t===r?l:t}
const StoreContext = createContext();
export const Provider = StoreContext.Provider;
function flatUpdate(state, update) {
let changed = false;
for (let i in update) {
if (state[i] !== update[i]) {
if (changed === false) {
changed = true;
state = Object.assign({}, state);
}
state[i] = update[i];
}
}
return state;
}
function createSelector(sel) {
let type = typeof sel;
if (type == 'function') return sel;
if (type == 'string') sel = sel.split(/\s*,\s*/);
if (Array.isArray(sel)) sel = sel.reduce((obj,key)=>((obj[key]=key), obj), {});
return state => {
let selected = {};
if (state) {
for (let key in sel) {
selected[key] = key in state ? state[key] : dlv(state, sel[key]);
}
}
return selected;
};
}
export function useStore() {
return useContext(StoreContext);
}
export function useStoreState(selector) {
const store = useContext(StoreContext);
const filter = useMemo(() => createSelector(selector), []);
const [state, setState] = useReducer(flatUpdate, store ? filter(store.getState()) : {});
useEffect(() => store.subscribe(state => setState(filter(state))), [store]);
return state;
}
function bindActionsToStore(actions, store) {
const bindings = store.bindings || (store.bindings = new (typeof WeakMap=='function' ? WeakMap : Map)());
let bound = bindings.get(actions);
if (!bound) {
bindings.set(actions, bound = {});
if (typeof actions === 'function') actions = actions(store);
for (let i in actions) bound[i] = store.action(actions[i]);
}
return bound;
}
export function useActions(actions, mapping, deps) {
if (typeof deps=='function') ([deps,mapping]=[mapping,deps]);
const store = useContext(StoreContext);
const boundActions = useMemo(
() => bindActionsToStore(actions, store),
[actions, store]
);
mapping = useCallback(mapping, deps || []);
const boundMapping = useMemo(() => {
let a = mapping, map = {};
if (!a) return boundActions;
if (typeof a === 'function') a = a(boundActions);
for (let i in a) {
let v = a[i];
if (Array.isArray(v)) {
v = v[0].bind.apply(v[0], v);
// const f = v[0];
// const a = v;
// v = s => f.apply(this, (a[0]=s, a));
}
// map[i] = store.action(v);
map[i] = v;
}
return map;
}, [boundActions, mapping]);
return boundMapping;
}
{
"name": "unistore-hooks",
"version": "0.1.1",
"main": "dist/unistore-hooks.js",
"module": "dist/unistore-hooks.module.js",
"unpkg": "dist/unistore-hooks.umd.js",
"repo": "https://gist.github.com/developit/ecd64416d955874c3426d4582045533a",
"scripts": {
"prepare": "microbundle index.js --no-sourcemap -f es,cjs",
"prepack": "mv *unistore-hooks.md README.md",
"postpack": "mv README.md *unistore-hooks.md"
},
"devDependencies": {
"microbundle": "^0.12.0",
"preact": "^10.4.4"
},
"peerDependencies": {
"preact": ">=10"
}
}
@developit
Copy link
Author

@tomBryer - none of that output is related to this package. unistore-hooks is 7 files and has no dependencies:
https://unpkg.com/browse/unistore-hooks@0.1.1/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment