jotai
Version:
👻 Next gen state management that will spook you
773 lines (753 loc) • 22.9 kB
JavaScript
import { RESET, unstable_NO_STORAGE_VALUE } from 'jotai/vanilla/utils';
export { RESET, unstable_NO_STORAGE_VALUE } from 'jotai/vanilla/utils';
import { atom, SECRET_INTERNAL_getScopeContext, useAtom, useSetAtom, SECRET_INTERNAL_registerPromiseAbort } from 'jotai';
export { useAtomValue, useSetAtom as useUpdateAtom } from 'jotai';
import { useContext, useCallback, useMemo } from 'react';
function atomWithReset(initialValue) {
const anAtom = atom(initialValue, (get, set, update) => {
const nextValue = typeof update === "function" ? update(get(anAtom)) : update;
set(anAtom, nextValue === RESET ? initialValue : nextValue);
});
return anAtom;
}
const WRITE_ATOM = "w";
const RESTORE_ATOMS = "h";
function useResetAtom(anAtom, scope) {
const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
const store = useContext(ScopeContext).s;
const setAtom = useCallback(
() => store[WRITE_ATOM](anAtom, RESET),
[store, anAtom]
);
return setAtom;
}
function useReducerAtom(anAtom, reducer, scope) {
const [state, setState] = useAtom(anAtom, scope);
const dispatch = useCallback(
(action) => {
setState((prev) => reducer(prev, action));
},
[setState, reducer]
);
return [state, dispatch];
}
function atomWithReducer(initialValue, reducer) {
const anAtom = atom(
initialValue,
(get, set, action) => set(anAtom, reducer(get(anAtom), action))
);
return anAtom;
}
function atomFamily(initializeAtom, areEqual) {
let shouldRemove = null;
const atoms = /* @__PURE__ */ new Map();
const createAtom = (param) => {
let item;
if (areEqual === void 0) {
item = atoms.get(param);
} else {
for (const [key, value] of atoms) {
if (areEqual(key, param)) {
item = value;
break;
}
}
}
if (item !== void 0) {
if (shouldRemove == null ? void 0 : shouldRemove(item[1], param)) {
createAtom.remove(param);
} else {
return item[0];
}
}
const newAtom = initializeAtom(param);
atoms.set(param, [newAtom, Date.now()]);
return newAtom;
};
createAtom.remove = (param) => {
if (areEqual === void 0) {
atoms.delete(param);
} else {
for (const [key] of atoms) {
if (areEqual(key, param)) {
atoms.delete(key);
break;
}
}
}
};
createAtom.setShouldRemove = (fn) => {
shouldRemove = fn;
if (!shouldRemove)
return;
for (const [key, value] of atoms) {
if (shouldRemove(value[1], key)) {
atoms.delete(key);
}
}
};
return createAtom;
}
const getWeakCacheItem = (cache, deps) => {
do {
const [dep, ...rest] = deps;
const entry = cache.get(dep);
if (!entry) {
return;
}
if (!rest.length) {
return entry[1];
}
cache = entry[0];
deps = rest;
} while (deps.length);
};
const setWeakCacheItem = (cache, deps, item) => {
do {
const [dep, ...rest] = deps;
let entry = cache.get(dep);
if (!entry) {
entry = [ new WeakMap()];
cache.set(dep, entry);
}
if (!rest.length) {
entry[1] = item;
return;
}
cache = entry[0];
deps = rest;
} while (deps.length);
};
const createMemoizeAtom = () => {
const cache = /* @__PURE__ */ new WeakMap();
const memoizeAtom = (createAtom, deps) => {
const cachedAtom = getWeakCacheItem(cache, deps);
if (cachedAtom) {
return cachedAtom;
}
const createdAtom = createAtom();
setWeakCacheItem(cache, deps, createdAtom);
return createdAtom;
};
return memoizeAtom;
};
const memoizeAtom$4 = createMemoizeAtom();
function selectAtom(anAtom, selector, equalityFn = Object.is) {
return memoizeAtom$4(() => {
const refAtom = atom(() => ({}));
const derivedAtom = atom((get) => {
const slice = selector(get(anAtom));
const ref = get(refAtom);
if ("prev" in ref && equalityFn(ref.prev, slice)) {
return ref.prev;
}
ref.prev = slice;
return slice;
});
return derivedAtom;
}, [anAtom, selector, equalityFn]);
}
function useAtomCallback(callback, scope) {
const anAtom = useMemo(
() => atom(
null,
(get, set, [arg, resolve, reject]) => {
try {
resolve(callback(get, set, arg));
} catch (e) {
reject(e);
}
}
),
[callback]
);
const invoke = useSetAtom(anAtom, scope);
return useCallback(
(arg) => {
let isSync = true;
let settled = {};
const promise = new Promise((resolve, reject) => {
invoke([
arg,
(v) => {
if (isSync) {
settled = { v };
} else {
resolve(v);
}
},
(e) => {
if (isSync) {
settled = { e };
} else {
reject(e);
}
}
]);
});
isSync = false;
if ("e" in settled) {
throw settled.e;
}
if ("v" in settled) {
return settled.v;
}
return promise;
},
[invoke]
);
}
const memoizeAtom$3 = createMemoizeAtom();
const deepFreeze = (obj) => {
if (typeof obj !== "object" || obj === null)
return;
Object.freeze(obj);
const propNames = Object.getOwnPropertyNames(obj);
for (const name of propNames) {
const value = obj[name];
deepFreeze(value);
}
return obj;
};
function freezeAtom(anAtom) {
return memoizeAtom$3(() => {
const frozenAtom = atom(
(get) => deepFreeze(get(anAtom)),
(_get, set, arg) => set(anAtom, arg)
);
return frozenAtom;
}, [anAtom]);
}
function freezeAtomCreator(createAtom) {
return (...params) => {
const anAtom = createAtom(...params);
const origRead = anAtom.read;
anAtom.read = (get) => deepFreeze(origRead(get));
return anAtom;
};
}
const memoizeAtom$2 = createMemoizeAtom();
const isWritable = (atom2) => !!atom2.write;
const isFunction = (x) => typeof x === "function";
function splitAtom(arrAtom, keyExtractor) {
return memoizeAtom$2(
() => {
const mappingCache = /* @__PURE__ */ new WeakMap();
const getMapping = (arr, prev) => {
let mapping = mappingCache.get(arr);
if (mapping) {
return mapping;
}
const prevMapping = prev && mappingCache.get(prev);
const atomList = [];
const keyList = [];
arr.forEach((item, index) => {
const key = keyExtractor ? keyExtractor(item) : index;
keyList[index] = key;
const cachedAtom = prevMapping && prevMapping.atomList[prevMapping.keyList.indexOf(key)];
if (cachedAtom) {
atomList[index] = cachedAtom;
return;
}
const read2 = (get) => {
const ref = get(refAtom);
const currArr = get(arrAtom);
const mapping2 = getMapping(currArr, ref.prev);
const index2 = mapping2.keyList.indexOf(key);
if (index2 < 0 || index2 >= currArr.length) {
const prevItem = arr[getMapping(arr).keyList.indexOf(key)];
if (prevItem) {
return prevItem;
}
throw new Error("splitAtom: index out of bounds for read");
}
return currArr[index2];
};
const write2 = (get, set, update) => {
const ref = get(refAtom);
const arr2 = get(arrAtom);
const mapping2 = getMapping(arr2, ref.prev);
const index2 = mapping2.keyList.indexOf(key);
if (index2 < 0 || index2 >= arr2.length) {
throw new Error("splitAtom: index out of bounds for write");
}
const nextItem = isFunction(update) ? update(arr2[index2]) : update;
set(arrAtom, [
...arr2.slice(0, index2),
nextItem,
...arr2.slice(index2 + 1)
]);
};
atomList[index] = isWritable(arrAtom) ? atom(read2, write2) : atom(read2);
});
if (prevMapping && prevMapping.keyList.length === keyList.length && prevMapping.keyList.every((x, i) => x === keyList[i])) {
mapping = prevMapping;
} else {
mapping = { atomList, keyList };
}
mappingCache.set(arr, mapping);
return mapping;
};
const refAtom = atom(() => ({}));
const read = (get) => {
const ref = get(refAtom);
const arr = get(arrAtom);
const mapping = getMapping(arr, ref.prev);
ref.prev = arr;
return mapping.atomList;
};
const write = (get, set, action) => {
if ("read" in action) {
console.warn("atomToRemove is deprecated. use action with type");
action = { type: "remove", atom: action };
}
switch (action.type) {
case "remove": {
const index = get(splittedAtom).indexOf(action.atom);
if (index >= 0) {
const arr = get(arrAtom);
set(arrAtom, [
...arr.slice(0, index),
...arr.slice(index + 1)
]);
}
break;
}
case "insert": {
const index = action.before ? get(splittedAtom).indexOf(action.before) : get(splittedAtom).length;
if (index >= 0) {
const arr = get(arrAtom);
set(arrAtom, [
...arr.slice(0, index),
action.value,
...arr.slice(index)
]);
}
break;
}
case "move": {
const index1 = get(splittedAtom).indexOf(action.atom);
const index2 = action.before ? get(splittedAtom).indexOf(action.before) : get(splittedAtom).length;
if (index1 >= 0 && index2 >= 0) {
const arr = get(arrAtom);
if (index1 < index2) {
set(arrAtom, [
...arr.slice(0, index1),
...arr.slice(index1 + 1, index2),
arr[index1],
...arr.slice(index2)
]);
} else {
set(arrAtom, [
...arr.slice(0, index2),
arr[index1],
...arr.slice(index2, index1),
...arr.slice(index1 + 1)
]);
}
}
break;
}
}
};
const splittedAtom = isWritable(arrAtom) ? atom(read, write) : atom(read);
return splittedAtom;
},
keyExtractor ? [arrAtom, keyExtractor] : [arrAtom]
);
}
function atomWithDefault(getDefault) {
const EMPTY = Symbol();
const overwrittenAtom = atom(EMPTY);
const anAtom = atom(
(get) => {
const overwritten = get(overwrittenAtom);
if (overwritten !== EMPTY) {
return overwritten;
}
return getDefault(get);
},
(get, set, update) => {
if (update === RESET) {
return set(overwrittenAtom, EMPTY);
}
return set(
overwrittenAtom,
typeof update === "function" ? update(get(anAtom)) : update
);
}
);
return anAtom;
}
const memoizeAtom$1 = createMemoizeAtom();
const emptyArrayAtom = atom(() => []);
function waitForAll(atoms) {
const createAtom = () => {
const unwrappedAtoms = unwrapAtoms(atoms);
const derivedAtom = atom((get) => {
const promises = [];
const values = unwrappedAtoms.map((anAtom, index) => {
try {
return get(anAtom);
} catch (e) {
if (e instanceof Promise) {
promises[index] = e;
} else {
throw e;
}
}
});
if (promises.length) {
throw Promise.all(promises);
}
return wrapResults(atoms, values);
});
return derivedAtom;
};
if (Array.isArray(atoms)) {
if (atoms.length) {
return memoizeAtom$1(createAtom, atoms);
}
return emptyArrayAtom;
}
return createAtom();
}
const unwrapAtoms = (atoms) => Array.isArray(atoms) ? atoms : Object.getOwnPropertyNames(atoms).map((key) => atoms[key]);
const wrapResults = (atoms, results) => Array.isArray(atoms) ? results : Object.getOwnPropertyNames(atoms).reduce(
(out, key, idx) => ({ ...out, [key]: results[idx] }),
{}
);
function createJSONStorage(getStringStorage) {
let lastStr;
let lastValue;
const storage = {
getItem: (key) => {
var _a, _b;
const parse = (str2) => {
str2 = str2 || "";
if (lastStr !== str2) {
try {
lastValue = JSON.parse(str2);
} catch {
return unstable_NO_STORAGE_VALUE;
}
lastStr = str2;
}
return lastValue;
};
const str = (_b = (_a = getStringStorage()) == null ? void 0 : _a.getItem(key)) != null ? _b : null;
if (str instanceof Promise) {
return str.then(parse);
}
return parse(str);
},
setItem: (key, newValue) => {
var _a;
return (_a = getStringStorage()) == null ? void 0 : _a.setItem(key, JSON.stringify(newValue));
},
removeItem: (key) => {
var _a;
return (_a = getStringStorage()) == null ? void 0 : _a.removeItem(key);
}
};
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
storage.subscribe = (key, callback) => {
const storageEventCallback = (e) => {
if (e.key === key && e.newValue) {
callback(JSON.parse(e.newValue));
}
};
window.addEventListener("storage", storageEventCallback);
return () => {
window.removeEventListener("storage", storageEventCallback);
};
};
}
return storage;
}
const defaultStorage = createJSONStorage(
() => typeof window !== "undefined" ? window.localStorage : void 0
);
function atomWithStorage(key, initialValue, storage = defaultStorage) {
const getInitialValue = () => {
const value = storage.getItem(key);
if (value instanceof Promise) {
return value.then((v) => v === unstable_NO_STORAGE_VALUE ? initialValue : v);
}
return value === unstable_NO_STORAGE_VALUE ? initialValue : value;
};
const baseAtom = atom(storage.delayInit ? initialValue : getInitialValue());
baseAtom.onMount = (setAtom) => {
let unsub;
if (storage.subscribe) {
unsub = storage.subscribe(key, setAtom);
setAtom(getInitialValue());
}
if (storage.delayInit) {
const value = getInitialValue();
if (value instanceof Promise) {
value.then(setAtom);
} else {
setAtom(value);
}
}
return unsub;
};
const anAtom = atom(
(get) => get(baseAtom),
(get, set, update) => {
const nextValue = typeof update === "function" ? update(get(baseAtom)) : update;
if (nextValue === RESET) {
set(baseAtom, initialValue);
return storage.removeItem(key);
}
set(baseAtom, nextValue);
return storage.setItem(key, nextValue);
}
);
return anAtom;
}
function atomWithHash(key, initialValue, options) {
const serialize = (options == null ? void 0 : options.serialize) || JSON.stringify;
const deserialize = (options == null ? void 0 : options.deserialize) || ((str) => {
try {
return JSON.parse(str || "");
} catch {
return unstable_NO_STORAGE_VALUE;
}
});
const subscribe = (options == null ? void 0 : options.subscribe) || ((callback) => {
window.addEventListener("hashchange", callback);
return () => {
window.removeEventListener("hashchange", callback);
};
});
const hashStorage = {
getItem: (key2) => {
if (typeof location === "undefined") {
return unstable_NO_STORAGE_VALUE;
}
const searchParams = new URLSearchParams(location.hash.slice(1));
const storedValue = searchParams.get(key2);
return deserialize(storedValue);
},
setItem: (key2, newValue) => {
const searchParams = new URLSearchParams(location.hash.slice(1));
searchParams.set(key2, serialize(newValue));
if (options == null ? void 0 : options.replaceState) {
history.replaceState(
null,
"",
location.pathname + location.search + "#" + searchParams.toString()
);
} else {
location.hash = searchParams.toString();
}
},
removeItem: (key2) => {
const searchParams = new URLSearchParams(location.hash.slice(1));
searchParams.delete(key2);
if (options == null ? void 0 : options.replaceState) {
history.replaceState(
null,
"",
location.pathname + location.search + "#" + searchParams.toString()
);
} else {
location.hash = searchParams.toString();
}
},
...(options == null ? void 0 : options.delayInit) && { delayInit: true },
subscribe: (key2, setValue) => {
const callback = () => {
const searchParams = new URLSearchParams(location.hash.slice(1));
const str = searchParams.get(key2);
if (str !== null) {
setValue(deserialize(str));
} else {
setValue(initialValue);
}
};
return subscribe(callback);
}
};
return atomWithStorage(key, initialValue, hashStorage);
}
function atomWithObservable(getObservable, options) {
const observableResultAtom = atom((get) => {
var _a;
let observable = getObservable(get);
const itself = (_a = observable[Symbol.observable]) == null ? void 0 : _a.call(observable);
if (itself) {
observable = itself;
}
let resolve;
const makePending = () => new Promise((r) => {
resolve = r;
});
const initialResult = options && "initialValue" in options ? {
d: typeof options.initialValue === "function" ? options.initialValue() : options.initialValue
} : makePending();
let setResult;
let lastResult;
const listener = (result) => {
lastResult = result;
resolve == null ? void 0 : resolve(result);
setResult == null ? void 0 : setResult(result);
};
let subscription;
let timer;
const isNotMounted = () => !setResult;
const start = () => {
if (subscription) {
clearTimeout(timer);
subscription.unsubscribe();
}
subscription = observable.subscribe({
next: (d) => listener({ d }),
error: (e) => listener({ e }),
complete: () => {
}
});
if (isNotMounted() && (options == null ? void 0 : options.unstable_timeout)) {
timer = setTimeout(() => {
if (subscription) {
subscription.unsubscribe();
subscription = void 0;
}
}, options.unstable_timeout);
}
};
start();
const resultAtom = atom(lastResult || initialResult);
resultAtom.onMount = (update) => {
setResult = update;
if (lastResult) {
update(lastResult);
}
if (subscription) {
clearTimeout(timer);
} else {
start();
}
return () => {
setResult = void 0;
if (subscription) {
subscription.unsubscribe();
subscription = void 0;
}
};
};
return [resultAtom, observable, makePending, start, isNotMounted];
});
const observableAtom = atom(
(get) => {
const [resultAtom] = get(observableResultAtom);
const result = get(resultAtom);
if ("e" in result) {
throw result.e;
}
return result.d;
},
(get, set, data) => {
const [resultAtom, observable, makePending, start, isNotMounted] = get(observableResultAtom);
if ("next" in observable) {
if (isNotMounted()) {
set(resultAtom, makePending());
start();
}
observable.next(data);
} else {
throw new Error("observable is not subject");
}
}
);
return observableAtom;
}
const hydratedMap = /* @__PURE__ */ new WeakMap();
function useHydrateAtoms(values, scope) {
const ScopeContext = SECRET_INTERNAL_getScopeContext(scope);
const scopeContainer = useContext(ScopeContext);
const store = scopeContainer.s;
const hydratedSet = getHydratedSet(scopeContainer);
const tuplesToRestore = [];
for (const tuple of values) {
const atom = tuple[0];
if (!hydratedSet.has(atom)) {
hydratedSet.add(atom);
tuplesToRestore.push(tuple);
}
}
if (tuplesToRestore.length) {
store[RESTORE_ATOMS](tuplesToRestore);
}
}
function getHydratedSet(scopeContainer) {
let hydratedSet = hydratedMap.get(scopeContainer);
if (!hydratedSet) {
hydratedSet = /* @__PURE__ */ new WeakSet();
hydratedMap.set(scopeContainer, hydratedSet);
}
return hydratedSet;
}
const memoizeAtom = createMemoizeAtom();
const LOADING = { state: "loading" };
function loadable(anAtom) {
return memoizeAtom(() => {
const loadableAtomCache = /* @__PURE__ */ new WeakMap();
const catchAtom = atom((get) => {
let promise;
try {
const data = get(anAtom);
const loadableAtom2 = atom({ state: "hasData", data });
return loadableAtom2;
} catch (error) {
if (error instanceof Promise) {
promise = error;
} else {
const loadableAtom2 = atom({
state: "hasError",
error
});
return loadableAtom2;
}
}
const cached = loadableAtomCache.get(promise);
if (cached) {
return cached;
}
const loadableAtom = atom(
LOADING,
async (get2, set) => {
try {
const data = await get2(anAtom, { unstable_promise: true });
set(loadableAtom, { state: "hasData", data });
} catch (error) {
set(loadableAtom, { state: "hasError", error });
}
}
);
loadableAtom.onMount = (init) => {
init();
};
loadableAtomCache.set(promise, loadableAtom);
return loadableAtom;
});
const derivedAtom = atom((get) => {
const loadableAtom = get(catchAtom);
return get(loadableAtom);
});
return derivedAtom;
}, [anAtom]);
}
function abortableAtom(read, write) {
return atom((get) => {
const controller = new AbortController();
const promise = read(get, { signal: controller.signal });
if (promise instanceof Promise) {
SECRET_INTERNAL_registerPromiseAbort(promise, () => controller.abort());
}
return promise;
}, write);
}
export { abortableAtom, atomFamily, atomWithDefault, atomWithHash, atomWithObservable, atomWithReducer, atomWithReset, atomWithStorage, createJSONStorage, freezeAtom, freezeAtomCreator, loadable, selectAtom, splitAtom, useAtomCallback, useHydrateAtoms, useReducerAtom, useResetAtom, waitForAll };