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
JavaScript
;
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