jotai
Version:
👻 Primitive and flexible state management for React
88 lines (87 loc) • 3.3 kB
JavaScript
import { atom } from '../../vanilla.js';
const getCached = (c, m, k) => (m.has(k) ? m : m.set(k, c())).get(k);
const cache1 = new WeakMap();
const memo2 = (create, dep1, dep2) => {
const cache2 = getCached(() => new WeakMap(), cache1, dep1);
return getCached(create, cache2, dep2);
};
const isPromiseLike = (p) => typeof p?.then === 'function';
const defaultFallback = () => undefined;
export function unwrap(anAtom, fallback = defaultFallback) {
return memo2(() => {
const promiseErrorCache = new WeakMap();
const promiseResultCache = new WeakMap();
const refreshAtom = atom(0);
const triggerRefreshAtom = atom([]);
triggerRefreshAtom.INTERNAL_onInit = (store) => {
store.set(triggerRefreshAtom, [
() => store.set(refreshAtom, (c) => c + 1),
]);
};
if (process.env.NODE_ENV !== 'production') {
refreshAtom.debugPrivate = true;
triggerRefreshAtom.debugPrivate = true;
}
const promiseAndValueAtom = atom((get) => {
get(refreshAtom);
let prev;
try {
prev = get(promiseAndValueAtom);
}
catch {
// ignore previous errors to avoid getting stuck in error state
}
const promise = get(anAtom);
if (!isPromiseLike(promise)) {
return { v: promise };
}
if (promise !== prev?.p) {
promise.then((v) => {
promiseResultCache.set(promise, v);
const [triggerRefresh] = get(triggerRefreshAtom);
triggerRefresh();
}, (e) => {
promiseErrorCache.set(promise, e);
const [triggerRefresh] = get(triggerRefreshAtom);
triggerRefresh();
});
}
if (promiseErrorCache.has(promise)) {
if (prev && 'e' in prev && prev.p === promise) {
return prev;
}
const e = promiseErrorCache.get(promise);
if (prev && 'v' in prev) {
return { p: promise, e, v: prev.v };
}
return { p: promise, e };
}
if (promiseResultCache.has(promise)) {
return {
p: promise,
v: promiseResultCache.get(promise),
};
}
if (prev && 'v' in prev) {
return { p: promise, f: fallback(prev.v), v: prev.v };
}
return { p: promise, f: fallback() };
});
// HACK to read PromiseAndValue atom before initialization
promiseAndValueAtom.init = undefined;
if (process.env.NODE_ENV !== 'production') {
promiseAndValueAtom.debugPrivate = true;
}
return atom((get) => {
const state = get(promiseAndValueAtom);
if ('e' in state) {
throw state.e;
}
if ('f' in state) {
// is pending
return state.f;
}
return state.v;
}, (_get, set, ...args) => set(anAtom, ...args));
}, anAtom, fallback);
}