Skip to content

Instantly share code, notes, and snippets.

@james2doyle
Last active December 2, 2020 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save james2doyle/5d72bbc83edbe51f45c117a7e2738323 to your computer and use it in GitHub Desktop.
Save james2doyle/5d72bbc83edbe51f45c117a7e2738323 to your computer and use it in GitHub Desktop.
Use hyperactiv to recreate hooks with Javascript Proxy objects. Supports Redux devtool
import setState from 'use-state.js';
const [state, onStateUpdated] = useState('my-state', { count: 1 });
console.log(state.count); // logs: "1"
// runs on init in order to compute dependencies
onStateUpdated((/* newState, originalState */) => {
console.log('stateUpdated', state.count); // logs: "stateUpdated 1"
});
state.count = state.count + 1; // logs: "stateUpdated 2"
// https://github.com/elbywan/hyperactiv#description
import hyperactiv from 'hyperactiv';
const { observe, computed } = hyperactiv;
const DEBUG = true;
const noop = () => ({});
const fakeRedux = DEBUG ? {
// eslint-disable-next-line no-console
init: console.log.bind(console),
subscribe: noop,
// eslint-disable-next-line no-console
send: console.log.bind(console),
} : {
init: noop,
subscribe: noop,
send: noop,
};
const extension = window.__REDUX_DEVTOOLS_EXTENSION__ || window.top.__REDUX_DEVTOOLS_EXTENSION__ || { connect: () => fakeRedux };
const ReduxTool = extension.connect({
trace: true,
});
/**
* Create an observable object and listen to the state
*
* @param {string} name
* @param {object} state
* @param {object} options = {}
*/
export default function useState(name, state, options = {}) {
const originalState = JSON.parse(JSON.stringify(state));
let binding = noop;
const observeOptions = {
deep: true,
batch: true, // debounce calls
bubble: true, // bubble needs to be true if you want to call the __handler with nested objects
...options
};
let observed = observe(state, observeOptions);
if (DEBUG) {
ReduxTool.subscribe(message => {
if (message.type === 'DISPATCH' && message.state) {
observed = observe(JSON.parse(message.state), observeOptions);
// call our original callback with the state being sent from the redux devtools
binding(observed, originalState);
}
});
// first run of the computed function will push the new state and trigger but
// we want to skip that state change as the history will have a double state
let skipFirstComputed = true;
observed.__handler = (/* keys, value, oldValue, observedObject */) => {
const payload = JSON.parse(JSON.stringify(observed));
if (!skipFirstComputed) {
ReduxTool.send({ type: `${name}-change`, payload }, observed);
}
skipFirstComputed = false;
};
// start with an empty state
ReduxTool.init({});
}
return [
observed,
(fn) => {
binding = fn;
return computed(binding.bind(null, observed, originalState));
}
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment