UNPKG

jotai

Version:

👻 Next gen state management that will spook you

905 lines (730 loc) • 24.4 kB
'use strict'; 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;