jotai
Version:
👻 Next gen state management that will spook you
905 lines (730 loc) • 24.4 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var useContextSelector = require('use-context-selector');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it;
if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
it = o[Symbol.iterator]();
return it.next.bind(it);
}
var hasInitialValue = function hasInitialValue(atom) {
return 'init' in atom;
};
var createState = function createState(initialValues) {
var state = {
a: new WeakMap(),
m: new Map(),
w: new Map()
};
if (initialValues) {
for (var _iterator = _createForOfIteratorHelperLoose(initialValues), _step; !(_step = _iterator()).done;) {
var _step$value = _step.value,
atom = _step$value[0],
value = _step$value[1];
var atomState = {
v: value,
r: 0,
d: new Map()
};
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
Object.freeze(atomState);
}
state.a.set(atom, atomState);
}
}
return state;
};
var getAtomState = function getAtomState(state, atom) {
return state.w.get(atom) || state.a.get(atom);
};
var copyWip = function copyWip(state, copyingState) {
return _extends({}, state, {
w: new Map([].concat(state.w, copyingState.w))
});
};
var wipAtomState = function wipAtomState(state, atom) {
var atomState = getAtomState(state, atom);
if (atomState) {
atomState = _extends({}, atomState); // copy
} else {
atomState = {
r: 0,
d: new Map()
};
if (hasInitialValue(atom)) {
atomState.v = atom.init;
}
}
var nextState = _extends({}, state, {
w: new Map(state.w).set(atom, atomState)
});
return [atomState, nextState];
};
var replaceDependencies = function replaceDependencies(state, atomState, dependencies) {
if (dependencies) {
atomState.d = new Map(Array.from(dependencies).map(function (a) {
var _getAtomState$r, _getAtomState;
return [a, (_getAtomState$r = (_getAtomState = getAtomState(state, a)) == null ? void 0 : _getAtomState.r) != null ? _getAtomState$r : 0];
}));
}
};
var setAtomValue = function setAtomValue(state, atom, value, dependencies, promise) {
var _wipAtomState = wipAtomState(state, atom),
atomState = _wipAtomState[0],
nextState = _wipAtomState[1];
if (promise && promise !== atomState.rp) {
return state;
}
delete atomState.re;
delete atomState.rp;
if (!('v' in atomState) || !Object.is(atomState.v, value)) {
atomState.v = value;
atomState.r++;
}
replaceDependencies(nextState, atomState, dependencies);
return nextState;
};
var setAtomReadError = function setAtomReadError(state, atom, error, dependencies, promise) {
var _wipAtomState2 = wipAtomState(state, atom),
atomState = _wipAtomState2[0],
nextState = _wipAtomState2[1];
if (promise && promise !== atomState.rp) {
return state;
}
delete atomState.rp;
atomState.re = error;
replaceDependencies(nextState, atomState, dependencies);
return nextState;
};
var setAtomReadPromise = function setAtomReadPromise(state, atom, promise, dependencies) {
var _wipAtomState3 = wipAtomState(state, atom),
atomState = _wipAtomState3[0],
nextState = _wipAtomState3[1];
atomState.rp = promise;
replaceDependencies(nextState, atomState, dependencies);
return nextState;
};
var setAtomWritePromise = function setAtomWritePromise(state, atom, promise) {
var _wipAtomState4 = wipAtomState(state, atom),
atomState = _wipAtomState4[0],
nextState = _wipAtomState4[1];
if (promise) {
atomState.wp = promise;
} else {
delete atomState.wp;
}
return nextState;
};
var readAtomState = function readAtomState(state, updateState, atom, force) {
if (!force) {
var atomState = getAtomState(state, atom);
if (atomState && Array.from(atomState.d.entries()).every(function (_ref) {
var a = _ref[0],
r = _ref[1];
var aState = getAtomState(state, a);
return aState && !aState.re && !aState.rp && aState.r === r;
})) {
return [atomState, state];
}
}
var asyncState = _extends({}, state, {
w: new Map()
}); // empty wip
var isSync = true;
var nextState = state;
var error;
var promise;
var value;
var dependencies = new Set();
try {
var promiseOrValue = atom.read(function (a) {
dependencies.add(a);
if (a !== atom) {
var _aState;
if (isSync) {
;
var _readAtomState = readAtomState(nextState, updateState, a);
_aState = _readAtomState[0];
nextState = _readAtomState[1];
} else {
;
var _readAtomState2 = readAtomState(asyncState, updateState, a);
_aState = _readAtomState2[0];
asyncState = _readAtomState2[1];
}
if (_aState.re) {
throw _aState.re; // read error
}
if (_aState.rp) {
throw _aState.rp; // read promise
}
return _aState.v; // value
} // a === atom
var aState = getAtomState(nextState, a);
if (aState) {
if (aState.rp) {
throw aState.rp; // read promise
}
return aState.v; // value
}
if (hasInitialValue(a)) {
return a.init;
}
throw new Error('no atom init');
});
if (promiseOrValue instanceof Promise) {
promise = promiseOrValue.then(function (value) {
updateState(function (prev) {
return setAtomValue(copyWip(prev, asyncState), atom, value, dependencies, promise);
});
}).catch(function (e) {
updateState(function (prev) {
return setAtomReadError(copyWip(prev, asyncState), atom, e instanceof Error ? e : new Error(e), dependencies, promise);
});
});
} else {
value = promiseOrValue;
}
} catch (errorOrPromise) {
if (errorOrPromise instanceof Promise) {
promise = errorOrPromise.then(function () {
updateState(function (prev) {
var _readAtomState3 = readAtomState(prev, updateState, atom, true),
nextNextState = _readAtomState3[1];
if (nextNextState.w.size) {
return nextNextState;
}
return prev;
});
});
} else if (errorOrPromise instanceof Error) {
error = errorOrPromise;
} else {
error = new Error(errorOrPromise);
}
}
if (error) {
nextState = setAtomReadError(nextState, atom, error, dependencies);
} else if (promise) {
nextState = setAtomReadPromise(nextState, atom, promise, dependencies);
} else {
nextState = setAtomValue(nextState, atom, value, dependencies);
}
isSync = false;
return [getAtomState(nextState, atom), nextState];
};
var readAtom = function readAtom(state, updateState, readingAtom) {
var _readAtomState4 = readAtomState(state, updateState, readingAtom),
atomState = _readAtomState4[0],
nextState = _readAtomState4[1]; // merge back wip
nextState.w.forEach(function (atomState, atom) {
state.w.set(atom, atomState);
});
return atomState;
};
var addAtom = function addAtom(state, updateState, addingAtom, useId) {
var mounted = state.m.get(addingAtom);
if (mounted) {
var dependents = mounted[0];
dependents.add(useId);
} else {
mountAtom(state, updateState, addingAtom, useId);
}
}; // XXX doesn't work with mutally dependent atoms
var canUnmountAtom = function canUnmountAtom(atom, dependents) {
return !dependents.size || dependents.size === 1 && dependents.has(atom);
};
var delAtom = function delAtom(state, deletingAtom, useId) {
var mounted = state.m.get(deletingAtom);
if (mounted) {
var dependents = mounted[0];
dependents.delete(useId);
if (canUnmountAtom(deletingAtom, dependents)) {
unmountAtom(state, deletingAtom);
}
}
};
var getDependents = function getDependents(state, atom) {
var mounted = state.m.get(atom);
var dependents = new Set(mounted == null ? void 0 : mounted[0]); // collecting from wip
state.w.forEach(function (aState, a) {
if (aState.d.has(atom)) {
dependents.add(a);
}
});
return dependents;
};
var updateDependentsState = function updateDependentsState(state, updateState, atom, prevAtomState) {
var _getAtomState2;
if (!prevAtomState || prevAtomState.r === ((_getAtomState2 = getAtomState(state, atom)) == null ? void 0 : _getAtomState2.r)) {
return state; // bail out
}
var dependents = getDependents(state, atom);
var nextState = state;
dependents.forEach(function (dependent) {
if (dependent === atom || typeof dependent === 'symbol') {
return;
}
var dependentState = getAtomState(nextState, dependent);
var _readAtomState5 = readAtomState(nextState, updateState, dependent, true),
nextDependentState = _readAtomState5[0],
nextNextState = _readAtomState5[1];
var promise = nextDependentState.rp;
if (promise) {
promise.then(function () {
updateState(function (prev) {
return updateDependentsState(prev, updateState, dependent, dependentState);
});
});
nextState = nextNextState;
} else {
nextState = updateDependentsState(nextNextState, updateState, dependent, dependentState);
}
});
return nextState;
};
var writeAtomState = function writeAtomState(state, updateState, atom, update, pendingPromises) {
var atomState = getAtomState(state, atom);
if (atomState && atomState.wp) {
var promise = atomState.wp.then(function () {
updateState(function (prev) {
return writeAtomState(prev, updateState, atom, update);
});
});
if (pendingPromises) {
pendingPromises.push(promise);
}
return state;
}
var nextState = state;
var isSync = true;
try {
var promiseOrVoid = atom.write(function (a) {
var aState = getAtomState(nextState, a);
if (!aState) {
if (hasInitialValue(a)) {
return a.init;
}
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
console.warn('Unable to read an atom without initial value in write function. Please useAtom in advance.', a);
}
throw new Error('uninitialized atom');
}
if (aState.rp && typeof process === 'object' && process.env.NODE_ENV !== 'production') {
// TODO will try to detect this
console.warn('Reading pending atom state in write operation. We need to detect this and fallback. Please file an issue with repro.', a);
}
return aState.v;
}, function (a, v) {
if (a === atom) {
var aState = getAtomState(nextState, a);
if (isSync) {
nextState = updateDependentsState(setAtomValue(nextState, a, v), updateState, a, aState);
} else {
updateState(function (prev) {
return updateDependentsState(setAtomValue(prev, a, v), updateState, a, aState);
});
}
} else {
if (isSync) {
nextState = writeAtomState(nextState, updateState, a, v);
} else {
updateState(function (prev) {
return writeAtomState(prev, updateState, a, v);
});
}
}
}, update);
if (promiseOrVoid instanceof Promise) {
if (pendingPromises) {
pendingPromises.push(promiseOrVoid);
}
nextState = setAtomWritePromise(nextState, atom, promiseOrVoid.then(function () {
updateState(function (prev) {
return setAtomWritePromise(prev, atom);
});
}));
}
} catch (e) {
if (pendingPromises && pendingPromises.length) {
pendingPromises.push(new Promise(function (_resolve, reject) {
reject(e);
}));
} else {
throw e;
}
}
isSync = false;
return nextState;
};
var writeAtom = function writeAtom(updateState, writingAtom, update) {
var pendingPromises = [];
updateState(function (prev) {
var nextState = writeAtomState(prev, updateState, writingAtom, update, pendingPromises);
return nextState;
});
if (pendingPromises.length) {
return new Promise(function (resolve, reject) {
var loop = function loop() {
var len = pendingPromises.length;
if (len === 0) {
resolve();
} else {
Promise.all(pendingPromises).then(function () {
pendingPromises.splice(0, len);
loop();
}).catch(reject);
}
};
loop();
});
}
};
var isActuallyWritableAtom = function isActuallyWritableAtom(atom) {
return !!atom.write;
};
var mountAtom = function mountAtom(state, updateState, atom, initialDependent) {
// mount dependencies beforehand
var atomState = getAtomState(state, atom);
if (atomState) {
atomState.d.forEach(function (_, a) {
if (a !== atom) {
// check if not mounted
if (!state.m.has(a)) {
mountAtom(state, updateState, a, atom);
}
}
});
} else if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
console.warn('[Bug] could not find atom state to mount', atom);
} // mount self
var onUmount;
if (isActuallyWritableAtom(atom) && atom.onMount) {
var setAtom = function setAtom(update) {
return writeAtom(updateState, atom, update);
};
onUmount = atom.onMount(setAtom);
}
state.m.set(atom, [new Set([initialDependent]), onUmount]);
};
var unmountAtom = function unmountAtom(state, atom) {
var _state$m$get;
// unmount self
var onUnmount = (_state$m$get = state.m.get(atom)) == null ? void 0 : _state$m$get[1];
if (onUnmount) {
onUnmount();
}
state.m.delete(atom); // unmount dependencies afterward
var atomState = getAtomState(state, atom);
if (atomState) {
if (atomState.rp && typeof process === 'object' && process.env.NODE_ENV !== 'production') {
console.warn('[Bug] deleting atomState with read promise', atom);
}
atomState.d.forEach(function (_, a) {
if (a !== atom) {
var _state$m$get2;
var dependents = (_state$m$get2 = state.m.get(a)) == null ? void 0 : _state$m$get2[0];
if (dependents) {
dependents.delete(atom);
if (canUnmountAtom(a, dependents)) {
unmountAtom(state, a);
}
}
}
});
} else if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
console.warn('[Bug] could not find atom state to unmount', atom);
}
};
var commitState = function commitState(state, updateState) {
if (state.w.size) {
// apply wip to MountedMap
state.w.forEach(function (atomState, atom) {
var _state$a$get;
var prevDependencies = (_state$a$get = state.a.get(atom)) == null ? void 0 : _state$a$get.d;
if (prevDependencies === atomState.d) {
return;
}
var dependencies = new Set(atomState.d.keys());
if (prevDependencies) {
prevDependencies.forEach(function (_, a) {
var mounted = state.m.get(a);
if (dependencies.has(a)) {
// not changed
dependencies.delete(a);
} else if (mounted) {
var dependents = mounted[0];
dependents.delete(atom);
if (canUnmountAtom(a, dependents)) {
unmountAtom(state, a);
}
} else if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
console.warn('[Bug] a dependency is not mounted', a);
}
});
}
dependencies.forEach(function (a) {
var mounted = state.m.get(a);
if (mounted) {
var dependents = mounted[0];
dependents.add(atom);
} else {
mountAtom(state, updateState, a, atom);
}
});
}); // copy wip to AtomStateMap
state.w.forEach(function (atomState, atom) {
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
Object.freeze(atomState);
}
state.a.set(atom, atomState);
}); // empty wip
state.w.clear();
}
};
var ContextsMap = new Map();
var getContexts = function getContexts(scope) {
if (!ContextsMap.has(scope)) {
ContextsMap.set(scope, [useContextSelector.createContext(null), useContextSelector.createContext(null)]);
}
return ContextsMap.get(scope);
};
var isReactExperimental = !!(typeof process === 'object' && process.env.IS_REACT_EXPERIMENTAL) || !!React__default['default'].unstable_useMutableSource;
var defaultContextUpdate = function defaultContextUpdate(f) {
return f();
};
var InnerProvider = function InnerProvider(_ref) {
var r = _ref.r,
c = _ref.c,
children = _ref.children;
var contextUpdate = useContextSelector.useContextUpdate(c);
if (isReactExperimental && r.current === defaultContextUpdate) {
r.current = function (f) {
return contextUpdate(f);
};
}
return children != null ? children : null;
};
var Provider = function Provider(_ref2) {
var initialValues = _ref2.initialValues,
scope = _ref2.scope,
children = _ref2.children;
var contextUpdateRef = React.useRef(defaultContextUpdate);
var _useState = React.useState(function () {
return createState(initialValues);
}),
state = _useState[0],
setState = _useState[1];
var lastStateRef = React.useRef(state);
var updateState = React.useCallback(function (updater) {
lastStateRef.current = updater(lastStateRef.current);
contextUpdateRef.current(function () {
setState(lastStateRef.current);
});
}, []);
React.useEffect(function () {
commitState(state, updateState);
lastStateRef.current = state;
});
var actions = React.useMemo(function () {
return {
add: function add(atom, id) {
addAtom(lastStateRef.current, updateState, atom, id);
},
del: function del(atom, id) {
delAtom(lastStateRef.current, atom, id);
},
read: function read(state, atom) {
return readAtom(state, updateState, atom);
},
write: function write(atom, update) {
return writeAtom(updateState, atom, update);
}
};
}, [updateState]);
if (typeof process === 'object' && process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useDebugState(state);
}
var _getContexts = getContexts(scope),
ActionsContext = _getContexts[0],
StateContext = _getContexts[1];
return React.createElement(ActionsContext.Provider, {
value: actions
}, React.createElement(StateContext.Provider, {
value: state
}, React.createElement(InnerProvider, {
r: contextUpdateRef,
c: StateContext
}, children)));
};
var atomToPrintable = function atomToPrintable(atom) {
return atom.debugLabel || atom.toString();
};
var isAtom = function isAtom(x) {
return typeof x !== 'symbol';
};
var stateToPrintable = function stateToPrintable(state) {
return Object.fromEntries(Array.from(state.m.entries()).map(function (_ref3) {
var atom = _ref3[0],
_ref3$ = _ref3[1],
dependents = _ref3$[0];
var atomState = state.a.get(atom) || {};
return [atomToPrintable(atom), {
value: atomState.re || atomState.rp || atomState.wp || atomState.v,
dependents: Array.from(dependents).filter(isAtom).map(atomToPrintable)
}];
}));
};
var useDebugState = function useDebugState(state) {
React.useDebugValue(state, stateToPrintable);
};
var keyCount = 0; // global key count for all atoms
function atom(read, write) {
var key = "atom" + ++keyCount;
var config = {
toString: function toString() {
return key;
}
};
if (typeof read === 'function') {
config.read = read;
} else {
config.init = read;
config.read = function (get) {
return get(config);
};
config.write = function (get, set, update) {
set(config, typeof update === 'function' ? update(get(config)) : update);
};
}
if (write) {
config.write = write;
}
return config;
}
function assertContextValue(x, scope) {
if (!x) {
throw new Error("Please use <Provider" + (scope ? " scope=" + String(scope) : '') + ">");
}
}
var isWritable = function isWritable(atom) {
return !!atom.write;
};
function useAtom(atom) {
var _getContexts = getContexts(atom.scope),
ActionsContext = _getContexts[0],
StateContext = _getContexts[1];
var actions = useContextSelector.useContext(ActionsContext);
assertContextValue(actions, atom.scope);
var value = useContextSelector.useContextSelector(StateContext, React.useCallback(function (state) {
assertContextValue(state);
var atomState = actions.read(state, atom);
if (atomState.re) {
throw atomState.re; // read error
}
if (atomState.rp) {
throw atomState.rp; // read promise
}
if (atomState.wp) {
throw atomState.wp; // write promise
}
if ('v' in atomState) {
return atomState.v;
}
throw new Error('no atom value');
}, [atom, actions]));
React.useEffect(function () {
var id = Symbol();
actions.add(atom, id);
return function () {
actions.del(atom, id);
};
}, [actions, atom]);
var setAtom = React.useCallback(function (update) {
if (isWritable(atom)) {
return actions.write(atom, update);
} else {
throw new Error('not writable atom');
}
}, [atom, actions]);
React.useDebugValue(value);
return [value, setAtom];
}
var useBridge = function useBridge(scope) {
var _getContexts = getContexts(scope),
ActionsContext = _getContexts[0],
StateContext = _getContexts[1];
var actions = useContextSelector.useBridgeValue(ActionsContext);
var state = useContextSelector.useBridgeValue(StateContext);
return React.useMemo(function () {
return [actions, state];
}, [actions, state]);
};
var Bridge = function Bridge(_ref) {
var value = _ref.value,
scope = _ref.scope,
children = _ref.children;
var actions = value[0],
state = value[1];
var _getContexts2 = getContexts(scope),
ActionsContext = _getContexts2[0],
StateContext = _getContexts2[1];
return React.createElement(useContextSelector.BridgeProvider, {
context: ActionsContext,
value: actions
}, React.createElement(useContextSelector.BridgeProvider, {
context: StateContext,
value: state
}, children));
};
exports.Bridge = Bridge;
exports.Provider = Provider;
exports.atom = atom;
exports.useAtom = useAtom;
exports.useBridge = useBridge;