UNPKG

jotai

Version:

👻 Next gen state management that will spook you

712 lines (692 loc) • 22.1 kB
import { atom, SECRET_INTERNAL_getScopeContext, useAtom, useSetAtom } from 'jotai'; export { useAtomValue, useSetAtom as useUpdateAtom } from 'jotai'; import { useContext, useCallback, useMemo } from 'react'; const RESET = Symbol(); function atomWithReset(initialValue) { const anAtom = atom(initialValue, (get, set, update) => { if (update === RESET) { set(anAtom, initialValue); } else { set(anAtom, typeof update === "function" ? update(get(anAtom)) : update); } }); 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)) { atoms.delete(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; } var __defProp$1 = Object.defineProperty; var __defProps$1 = Object.defineProperties; var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; var __hasOwnProp$1 = Object.prototype.hasOwnProperty; var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues$1 = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); if (__getOwnPropSymbols$1) for (var prop of __getOwnPropSymbols$1(b)) { if (__propIsEnum$1.call(b, prop)) __defNormalProp$1(a, prop, b[prop]); } return a; }; var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); const memoizeAtom$1 = createMemoizeAtom(); 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)) { return memoizeAtom$1(createAtom, atoms); } 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) => __spreadProps$1(__spreadValues$1({}, out), { [key]: results[idx] }), {}); var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); function createJSONStorage(getStringStorage) { let lastStr; let lastValue; return { getItem: (key) => { const parse = (str2) => { str2 = str2 || ""; if (lastStr !== str2) { lastValue = JSON.parse(str2); lastStr = str2; } return lastValue; }; const str = getStringStorage().getItem(key); if (str instanceof Promise) { return str.then(parse); } return parse(str); }, setItem: (key, newValue) => getStringStorage().setItem(key, JSON.stringify(newValue)), removeItem: (key) => getStringStorage().removeItem(key) }; } const defaultStorage = createJSONStorage(() => localStorage); defaultStorage.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); }; }; function atomWithStorage(key, initialValue, storage = defaultStorage) { const getInitialValue = () => { try { const value = storage.getItem(key); if (value instanceof Promise) { return value.catch(() => initialValue); } return value; } catch { return initialValue; } }; 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) => { if (update === RESET) { set(baseAtom, initialValue); return storage.removeItem(key); } const newValue = typeof update === "function" ? update(get(baseAtom)) : update; set(baseAtom, newValue); return storage.setItem(key, newValue); }); 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) || JSON.parse; const subscribe = (options == null ? void 0 : options.subscribe) || ((callback) => { window.addEventListener("hashchange", callback); return () => { window.removeEventListener("hashchange", callback); }; }); const hashStorage = __spreadProps(__spreadValues({ getItem: (key2) => { const searchParams = new URLSearchParams(location.hash.slice(1)); const storedValue = searchParams.get(key2); if (storedValue === null) { throw new Error("no value stored"); } 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, "", "#" + 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, "", "#" + 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(createObservable, options) { const observableResultAtom = atom((get) => { var _a; let observable = createObservable(get); const itself = (_a = observable[Symbol.observable]) == null ? void 0 : _a.call(observable); if (itself) { observable = itself; } const dataAtom = atom((options == null ? void 0 : options.initialValue) ? getInitialValue(options) : firstValueFrom(observable)); let setData = () => { throw new Error("setting data without mount"); }; const dataListener = (data) => { setData(data); }; const errorListener = (error) => { setData(Promise.reject(error)); }; let subscription = null; dataAtom.onMount = (update) => { setData = update; if (!subscription) { subscription = observable.subscribe(dataListener, errorListener); } return () => { subscription == null ? void 0 : subscription.unsubscribe(); subscription = null; }; }; return { dataAtom, observable }; }); const observableAtom = atom((get) => { const { dataAtom } = get(observableResultAtom); return get(dataAtom); }, (get, _set, data) => { const { observable } = get(observableResultAtom); if ("next" in observable) { observable.next(data); } else { throw new Error("observable is not subject"); } }); return observableAtom; } function getInitialValue(options) { const initialValue = options.initialValue; return initialValue instanceof Function ? initialValue() : initialValue; } function firstValueFrom(source) { return new Promise((resolve, reject) => { let resolved = false; const subscription = source.subscribe({ next: (value) => { resolve(value); resolved = true; if (subscription) { subscription.unsubscribe(); } }, error: reject, complete: () => { reject(); } }); if (resolved) { subscription.unsubscribe(); } }); } 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]); } export { RESET, atomFamily, atomWithDefault, atomWithHash, atomWithObservable, atomWithReducer, atomWithReset, atomWithStorage, createJSONStorage, freezeAtom, freezeAtomCreator, loadable, selectAtom, splitAtom, useAtomCallback, useHydrateAtoms, useReducerAtom, useResetAtom, waitForAll };