UNPKG

get-set-react

Version:

A React State Management Library similar and alternative to mobx, redux.etc. it's the simplest, smallest and fastest state management library for react with dev tools support.

771 lines (766 loc) 26.3 kB
'use strict'; var react = require('react'); let listenToGet = false; let storedUsed = new Set(); const setListenToGet = (value) => { listenToGet = value; }; const getUsedStores = () => { return storedUsed; }; const setUsedStores = (value) => { storedUsed = value; }; const addUsedStore = (value) => { storedUsed.add(value); }; function debounce(func, delay) { let timerId; return function (...args) { const context = this; clearTimeout(timerId); timerId = setTimeout(() => { func.apply(context, args); }, delay); }; } function getset(initialState, actions, config = { deepFreeze: true, supportSettersAndInheritance: true, }) { let versions = {}; let state1 = initialState; let state2 = typeof initialState === "object" ? createStateProxy(state1) : state1; const subscriptions = new Set(); const reactions = new Set(); let accumulatedChanges = new Map(); let alreadyDestructuredPaths = []; let setInProgress = false; let subscriptionCallInProgress = false; let stopCallingSubscriptions = false; let reactionInProgress = false; let functionName = ""; let fileName = ""; if (config.supportSettersAndInheritance) { //@ts-ignore const methodProps = typeof state1 === "object" && state1 !== null && !Array.isArray(state1) ? getAllMethodProps(state1) : []; if (methodProps.length > 0) { const allProps = getAllProps(state1); state1 = {}; allProps.forEach((prop) => { //@ts-ignore const temp = initialState[prop]; //@ts-ignore if (typeof temp === "function") { //@ts-ignore state1[prop] = function (...args) { return prop.startsWith("_") || prop.startsWith("action_") ? temp.call(state1, ...args) : dispatch(temp, ...args); }; } else { //@ts-ignore state1[prop] = temp; } }); } } config.deepFreeze && recursiveDeepFreeze(state1); initialState = state1; function clearAccumulatedChanges() { accumulatedChanges.clear(); alreadyDestructuredPaths = []; } function updateValue(obj, path, val, isDelete = false) { if (typeof obj !== "object" || obj === null) { return; } let pathAccumulated = ""; const keys = path.split("."); let currentObj = obj; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; pathAccumulated = pathAccumulated.length > 0 ? pathAccumulated + "." + key : key; if (!alreadyDestructuredPaths.includes(pathAccumulated)) { if (currentObj.hasOwnProperty(key) && typeof currentObj[key] === "object") { if (Array.isArray(currentObj[key])) { currentObj[key] = [...currentObj[key]]; } else { currentObj[key] = Object.assign({}, currentObj[key]); } alreadyDestructuredPaths.push(pathAccumulated); currentObj = currentObj[key]; } else { // If the key doesn't exist or is not an object, create an empty object currentObj[key] = {}; currentObj = currentObj[key]; } } else { currentObj = currentObj[key]; } } //@ts-ignore if (isDelete) { delete currentObj[keys[keys.length - 1]]; } else { currentObj[keys[keys.length - 1]] = val; } } function createStateProxy(obj, path = "", setterName) { return new Proxy(obj, { get(_target, key) { const fullPath = path ? `${path}.${key}` : key; //@ts-ignore let obj = fullPath.split(".").reduce((a, b) => a[b], state1); if (typeof obj === "object" && obj !== null) { //@ts-ignore return createStateProxy(config.deepFreeze ? Array.isArray(obj) ? [...obj] : Object.assign({}, obj) : obj, path ? `${path}.${key}` : key, setterName); } else { return obj; } }, set(target, key, value) { const fullPath = path ? `${path}.${key}` : key; const oldValue = target[key]; updateValue(state1, fullPath, value); const change = { path: fullPath, from: reactionInProgress ? "react" : "update", type: oldValue === undefined ? "add" : "update", value, functionName: setterName || functionName, fileName: setterName ? "Setter" : fileName, id: Math.random().toString(), }; addChangeToList(change); return true; }, deleteProperty(target, key) { const fullPath = path ? `${path}.${key}` : key; const oldValue = target[key]; updateValue(state1, fullPath, undefined, true); delete target[key]; const change = { path: fullPath, functionName, fileName, from: reactionInProgress ? "react" : "update", type: "delete", value: oldValue, id: Math.random().toString(), }; addChangeToList(change); return true; }, }); } function addChangeToList(change) { accumulatedChanges.set(change.path, change); } let reactionCallInProgress = false; const callReactions = () => { reactionCallInProgress = true; const changes = [...accumulatedChanges.values()].reverse(); for (const reaction of reactions) { try { !stopCallingSubscriptions && reaction(changes); } catch (e) { console.error(e); } } reactionCallInProgress = false; }; const callSubs = debounce(() => { config.deepFreeze && recursiveDeepFreeze(state1); subscriptionCallInProgress = true; const changes = [...accumulatedChanges.values()].reverse(); for (const subscription of [...subscriptions]) { !stopCallingSubscriptions && subscription.fn(changes, subscription.type); } stopCallingSubscriptions = false; clearAccumulatedChanges(); subscriptionCallInProgress = false; }, 0); function get() { if (listenToGet) { addUsedStore(val); } return state1; } function set(newState, saveCurrentStateAs = "") { checkIfOtherCallInProgress(); if (!reactionInProgress) { getCallerInfo(); } monitorUpdates(); setInProgress = true; const newValue = typeof newState === "function" ? newState(state1) : newState; state1 = newValue; state2 = typeof state1 === "object" ? createStateProxy(state1, "") : state1; addChangeToList({ from: "set", functionName, fileName, type: "update", path: "", id: Math.random().toString(), value: newValue, }); saveCurrentStateAs && (versions[saveCurrentStateAs] = state1); setInProgress = false; callReactions(); callSubs(); // updateState1(true); functionName = ""; fileName = ""; return state1; } function checkIfOtherCallInProgress() { if (reactionInProgress || reactionCallInProgress) { throw Error("update/set calls are not allowed in reactions. instead directly change the state"); } if (setInProgress) { throw Error("update/set calls are not allowed from within the call backs of update/set"); } if (subscriptionCallInProgress) { throw Error("update/set calls are not allowed from the subscriptions to prevent memory leaks. Use Reactions Instead"); } } function getCallerInfo(stackIndex = 3) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; try { const error = new Error(); const stackLines = (_b = (_a = error === null || error === void 0 ? void 0 : error.stack) === null || _a === void 0 ? void 0 : _a.split) === null || _b === void 0 ? void 0 : _b.call(_a, "\n"); const temp = (_f = (_e = (_d = (_c = stackLines === null || stackLines === void 0 ? void 0 : stackLines[stackIndex]) === null || _c === void 0 ? void 0 : _c.trim) === null || _d === void 0 ? void 0 : _d.call(_c)) === null || _e === void 0 ? void 0 : _e.split) === null || _f === void 0 ? void 0 : _f.call(_e, " "); const a = (_k = (_j = (_h = (_g = temp === null || temp === void 0 ? void 0 : temp[temp.length > 2 ? 2 : 1]) === null || _g === void 0 ? void 0 : _g.split("?")) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.split) === null || _k === void 0 ? void 0 : _k.call(_j, "/"); const b = (temp === null || temp === void 0 ? void 0 : temp.length) || 0 > 2 ? temp === null || temp === void 0 ? void 0 : temp[1] : ""; (functionName = ((temp === null || temp === void 0 ? void 0 : temp.length) || 0) < 3 ? "unknown" : b || ""), (fileName = (a === null || a === void 0 ? void 0 : a[(a === null || a === void 0 ? void 0 : a.length) - 1]) || ""); } catch (error) { } } let dispatchInProgress = false; function preUpdateAndDispatch() { monitorUpdates(); checkIfOtherCallInProgress(); getCallerInfo(); setInProgress = true; } function dispatch(setter, ...args) { if (!dispatchInProgress) { try { dispatchInProgress = true; preUpdateAndDispatch(); updateOrDispatch(setter, undefined, true, ...args); dispatchInProgress = false; } catch (e) { console.error(e); } finally { dispatchInProgress = false; } return state1; } else { setter.call(state2, ...args); return state1; } } function updateOrDispatch(setter, saveCurrentStateAs, fromDispatch = false, ...args) { try { if (typeof setter === "function" && typeof state1 === "object") { const oldState = state1; state1 = shallowClone(state1); state2 = createStateProxy(state1, "", setter.name); fromDispatch ? setter.call(state2, ...args) : setter(state2, saveCurrentStateAs); setInProgress = false; alreadyDestructuredPaths = []; if ([...accumulatedChanges].length === 0) { state1 = oldState; } else { callReactions(); callSubs(); } } else { throw Error("update function cant be used for primitive states. you can use either set or val but pass updated values instead of function to update state"); } } catch (e) { console.error(e); } finally { fileName = ""; functionName = ""; setInProgress = false; } } function update(newState, saveCurrentStateAs) { preUpdateAndDispatch(); updateOrDispatch(newState, saveCurrentStateAs, false); return state1; } function subscribe(callback, type = "") { let obj = { fn: callback, type }; subscriptions.add(obj); return () => { subscriptions.delete(obj); }; } let callCount = 0; let lastCalledTimestamp = Date.now(); function monitorUpdates() { callCount++; let now = Date.now(); if (now - lastCalledTimestamp < 1000) { // Check if 1 second has elapsed if (callCount > 100) { callCount = 0; lastCalledTimestamp = now; stopCallingSubscriptions = true; throw Error("update / set functions are called more than 100 times in 1 second, check if this is because you are changing the state that's being the dependency of the reaction."); } // Reset the counter and timestamp for the next interval } else { callCount = 0; lastCalledTimestamp = now; } } function react(fn, dependencyArrayFn) { getCallerInfo(3); let a = functionName; let b = fileName; functionName = ""; fileName = ""; let oldRefs = dependencyArrayFn(get()); reactions.add((changes) => { functionName = a; fileName = b; reactionInProgress = true; const newRefs = dependencyArrayFn(get()); try { if (checkIfDepsRefsChanged(oldRefs, newRefs)) { oldRefs = newRefs; state2 = createStateProxy(state1); fn(state2, changes); } } catch (e) { console.error("error occured in reaction", e); } finally { functionName = ""; fileName = ""; reactionInProgress = false; } }); } function checkIfDepsRefsChanged(oldRef, newRef) { if (oldRef.length !== newRef.length) { return true; } for (let i = 0; i < oldRef.length; i++) { if (oldRef[i] !== newRef[i]) { return true; } } return false; } const _actions = actions && typeof actions === "function" ? actions({ get, set, update, val }) : undefined; if (_actions && Object.keys(_actions).length > 0) { Object.keys(_actions).forEach((key) => { //@ts-ignore if (typeof _actions[key] === "function") { //@ts-ignore _actions[key] = _actions[key].bind(_actions); } }); } function val(newStateFn, versionName) { if (arguments.length === 0) { return get(); } else { if (typeof newStateFn === "function") { return update(newStateFn, versionName); } else if (arguments.length > 0) { //@ts-ignore return set(newStateFn, versionName); } } return get(); } val.___25304743758287906getset = true; val.get = get; val.getState = get; val.react = react; // deepClone: () => (structuredClone || deepClone)(get()), val.reset = () => { set(initialState); }; val.set = set; val.update = update; val.saveVersion = (name) => { if (name) { versions[name] = state1; } }; val.val = val; val.getSavedVersion = (name) => { return versions[name]; }; val.actions = _actions; val.subscribe = subscribe; // dispatch, return val; } const create$1 = getset; function shallowClone(obj) { return Array.isArray(obj) ? [...obj] : typeof obj === "object" ? Object.assign({}, obj) : obj; } function getAllProps(instance) { var _a, _b; const props = Object.keys(instance); let currentPrototype = Object.getPrototypeOf(instance); while (currentPrototype !== null && currentPrototype !== Object.prototype) { props.push(...Object.getOwnPropertyNames(currentPrototype)); currentPrototype = Object.getPrototypeOf(currentPrototype); } const unique = [...(((_b = (_a = new Set(props)) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.call(_a)) || [])].filter((item) => item !== "constructor"); return unique; } function getAllStates(state) { const props = getAllProps(state); const newState = {}; props.forEach((prop) => { newState[prop] = state[prop]; //@ts-ignore if ( //@ts-ignore state[prop].getState && //@ts-ignore state[prop].___25304743758287906getset) { //@ts-ignore newState[prop] = state[prop].getState(); } else if (typeof state[prop] === "function") { //@ts-ignore newState[prop] = state[prop].bind(state); } }); return newState; } function subscribeToAll(fn, state) { const props = getAllProps(state); const unsubs = []; const unsubscribe = () => { unsubs.forEach((i) => i()); }; props.forEach((prop) => { //@ts-ignore if (state[prop] && typeof state[prop] !== "function" && //@ts-ignore state[prop].getState) { //@ts-ignore unsubs.push(state[prop].subscribe(fn, prop)); } }); return unsubscribe; } function createBubble$1(state) { let s = getAllStates(state); const fnToSubscribe = () => { s = getAllStates(state); }; const reset = () => { Object.keys(state).forEach((key) => { var _a, _b; //@ts-ignore (_b = (_a = state[key]).reset) === null || _b === void 0 ? void 0 : _b.call(_a); }); }; // subscribeToAll(fnToSubscribe, state); const bubble = Object.assign(Object.assign({}, state), { getState() { if (listenToGet) { addUsedStore(bubble); } return s; }, reset, set(newStateOrFunction) { let obj = typeof newStateOrFunction === "function" ? newStateOrFunction(s) : newStateOrFunction; Object.keys(obj).forEach((key) => { //@ts-ignore state[key].set(obj[key]); }); fnToSubscribe(); return this.getState(); }, get() { return this.getState(); }, subscribe: (fn) => subscribeToAll((changes = [], type = "") => { fnToSubscribe(); fn(changes.map((item) => (Object.assign(Object.assign({}, item), { path: type + "." + item.path })))); }, state) }); return bubble; } function createMemo$1(fn, deps = () => []) { let returnVal = null; let subs = []; let unsubs = []; let oldDeps = deps(); const run = () => { setUsedStores(new Set()); unsubs.forEach((i) => i()); setListenToGet(true); try { returnVal = fn(); } catch (e) { console.error("error occured in memo", e); } finally { setListenToGet(false); } storeVal.value = returnVal; getUsedStores().forEach((store) => { unsubs.push(store.subscribe((_changes) => { if (checkIfDepsChanged(oldDeps, deps()) || deps().length === 0) { run(); oldDeps = deps(); subs.forEach((i) => i()); } })); }); }; const get = () => { if (listenToGet) { addUsedStore(storeVal); } return returnVal; }; var storeVal = { getState: get, get: get, subscribe: (fn) => { subs.push(fn); return () => { subs = subs.filter((i) => i !== fn); }; }, value: returnVal, }; run(); return storeVal; } function checkIfDepsChanged(oldDeps, newDeps) { if (oldDeps.length !== newDeps.length) return true; for (let i = 0; i < oldDeps.length; i++) { if (oldDeps[i] !== newDeps[i]) return true; } return false; } function createEffect$1(fn, deps = () => []) { let unsubs = []; let oldDeps = deps(); const run = () => { setUsedStores(new Set()); unsubs.forEach((i) => i()); setListenToGet(true); try { fn(); } catch (e) { console.error("error occured in effect", e); } finally { setListenToGet(false); } getUsedStores().forEach((store) => { unsubs.push(store.subscribe((_changes) => { setTimeout(() => { if (checkIfDepsChanged(oldDeps, deps())) { run(); oldDeps = deps(); } }, 0); })); }); }; run(); } function recursiveDeepFreeze(obj) { Object.keys(obj).forEach((key) => { if (typeof obj[key] === "object" && obj[key] !== null && !Object.isFrozen(obj[key])) { recursiveDeepFreeze(obj[key]); } }); return Object.freeze(obj); } // function getAllNonMethodProps(obj: any): string[] { // // Traverse prototype chain to get all non-method properties // return getAllProps(obj).filter((key) => typeof obj[key] !== "function"); // } function getAllMethodProps(obj) { // Traverse prototype chain to get all method properties return getAllProps(obj).filter((key) => typeof obj[key] === "function"); } const atom$1 = getset; const molecule$1 = createBubble$1; const create = create$1; const useGet = (getSetInstance, resetOnUnmount = false) => { const [state, setState] = react.useState(getSetInstance.getState()); react.useEffect(() => { const fn = () => { setState(getSetInstance.getState()); }; return getSetInstance.subscribe(fn); }, [setState]); react.useEffect(() => { return () => { resetOnUnmount && getSetInstance.reset(); }; }, []); //@ts-ignore return react.useMemo(() => { return typeof state === "object" ? Object.assign(Object.assign({}, state), ((getSetInstance === null || getSetInstance === void 0 ? void 0 : getSetInstance.actions) || {})) : state; }, [state]); }; const useVal = (getSetInstance, resetOnUnmount = false) => { const val = react.useCallback(function (newStateFn, versionName) { if (arguments.length === 0) { return getSetInstance.val(); } else { return getSetInstance.val(newStateFn, versionName); } }, [getSetInstance.get()]); val.actions = getSetInstance.actions; useGet(getSetInstance, resetOnUnmount); return val; }; const useStore = (state, resetOnUnmount = false) => { //@ts-ignore const s = useGet(state, resetOnUnmount); return [s, state.actions]; }; function useVS(initialState, key) { const state = react.useRef(create(initialState)); const _val = state.current.val; const [count, setCount] = react.useState(0); const update = () => { if (key) { setTimeout(() => viewStore.set((s) => (Object.assign(Object.assign({}, s), { [key]: _val() }))), 100); } }; const val = react.useCallback( // here props should be T | undefined | ((currentState: T) => T) function (newState, versionName) { /// make it if else ///convert the below ternary to if else if (arguments.length > 0) { setCount(count + 1); return _val(newState, versionName); } else return _val(); }, [count]); //update(); useOnlyOnce(update); react.useEffect(() => { viewStore.get(); const unsub = state.current.subscribe(() => { setCount(count + 1); update(); }); return () => { var _a; unsub(); if (key && ((_a = viewStore.get()) === null || _a === void 0 ? void 0 : _a[key])) { viewStore.update((s) => delete s[key]); } }; }, []); return val; } const viewStore = create({}); const createBubble = createBubble$1; function useReset(stateOrStore) { react.useEffect(() => { return stateOrStore.forEach((store) => store.reset); }, []); } const useOnlyOnce = (fn) => { const hasBeenUsed = react.useRef(false); if (hasBeenUsed.current === false) { fn(); hasBeenUsed.current = true; } }; function useSelector(getSetInstance, selectorFn) { const [state, setState] = react.useState(selectorFn(getSetInstance.getState())); react.useEffect(() => { const fn = () => { setState(selectorFn(getSetInstance.getState())); }; return getSetInstance.subscribe(fn); }, [setState]); return state; } const useMemoValue = (memo) => { return react.useSyncExternalStore(memo.subscribe, memo.get); }; const useState = (initialState) => { const store = react.useRef(create(initialState)); return [useGet(store.current), store.current.val]; }; const createEffect = createEffect$1; const createMemo = createMemo$1; const atom = atom$1; const molecule = molecule$1; exports.atom = atom; exports.create = create; exports.createBubble = createBubble; exports.createEffect = createEffect; exports.createMemo = createMemo; exports.molecule = molecule; exports.useGet = useGet; exports.useMemoValue = useMemoValue; exports.useOnlyOnce = useOnlyOnce; exports.useReset = useReset; exports.useSelector = useSelector; exports.useState = useState; exports.useStore = useStore; exports.useVS = useVS; exports.useVal = useVal; exports.viewStore = viewStore; //# sourceMappingURL=index.js.map