UNPKG

recoil

Version:

Recoil - A state management library for React

1,643 lines (1,332 loc) 189 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react-dom'), require('react')) : typeof define === 'function' && define.amd ? define(['exports', 'react-dom', 'react'], factory) : (global = global || self, factory(global.Recoil = {}, global.ReactDOM, global.React)); }(this, (function (exports, reactDom, react) { 'use strict'; reactDom = reactDom && Object.prototype.hasOwnProperty.call(reactDom, 'default') ? reactDom['default'] : reactDom; react = react && Object.prototype.hasOwnProperty.call(react, 'default') ? react['default'] : react; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ const gks = new Map(); function Recoil_gkx(gk) { var _gks$get; return (_gks$get = gks.get(gk)) !== null && _gks$get !== void 0 ? _gks$get : false; } Recoil_gkx.setPass = gk => { gks.set(gk, true); }; Recoil_gkx.setFail = gk => { gks.set(gk, false); }; var Recoil_gkx_1 = Recoil_gkx; // @oss-only /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ /** * Returns a new Map object with the same keys as the original, but with the * values replaced with the output of the given callback function. */ function mapMap(map, callback) { const result = new Map(); map.forEach((value, key) => { result.set(key, callback(value, key)); }); return result; } var Recoil_mapMap = mapMap; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ function nullthrows(x, message) { if (x != null) { return x; } throw new Error(message !== null && message !== void 0 ? message : 'Got unexpected null or undefined'); } var Recoil_nullthrows = nullthrows; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ function recoverableViolation(message, projectName, { error } = {}) { { console.error(message, error); } return null; } var recoverableViolation_1 = recoverableViolation; // @oss-only var Recoil_recoverableViolation = recoverableViolation_1; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * Interface for `scheduler/tracing` to aid in profiling Recoil and Recoil apps. * * @emails oncall+recoil * * @format */ // flowlint-next-line untyped-import:off // @fb-only: const SchedulerTracing = require('SchedulerTracing'); function trace(message, node, fn) { // prettier-ignore // @fb-only: if (__DEV__) { // prettier-ignore // @fb-only: if ( // prettier-ignore // @fb-only: SchedulerTracing.unstable_trace !== undefined && // prettier-ignore // @fb-only: window.performance !== undefined // prettier-ignore // @fb-only: ) { // prettier-ignore // @fb-only: return SchedulerTracing.unstable_trace( // prettier-ignore // @fb-only: `Recoil: ${message} for node: ${ // prettier-ignore // @fb-only: typeof node === 'string' ? node : node.key // prettier-ignore // @fb-only: }`, // prettier-ignore // @fb-only: window.performance.now(), // prettier-ignore // @fb-only: fn, // prettier-ignore // @fb-only: ); // prettier-ignore // @fb-only: } // prettier-ignore // @fb-only: } return fn(); } function wrap(fn) { // prettier-ignore // @fb-only: if (__DEV__) { // prettier-ignore // @fb-only: if (SchedulerTracing.unstable_wrap !== undefined) { // prettier-ignore // @fb-only: return SchedulerTracing.unstable_wrap(fn); // prettier-ignore // @fb-only: } // prettier-ignore // @fb-only: } return fn; } var Recoil_Tracing = { trace, wrap }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * Utilities for working with built-in Maps and Sets without mutating them. * * @emails oncall+recoil * * @format */ function setByAddingToSet(set, v) { const next = new Set(set); next.add(v); return next; } function setByDeletingFromSet(set, v) { const next = new Set(set); next.delete(v); return next; } function mapBySettingInMap(map, k, v) { const next = new Map(map); next.set(k, v); return next; } function mapByUpdatingInMap(map, k, updater) { const next = new Map(map); next.set(k, updater(next.get(k))); return next; } function mapByDeletingFromMap(map, k) { const next = new Map(map); next.delete(k); return next; } function mapByDeletingMultipleFromMap(map, ks) { const next = new Map(map); ks.forEach(k => next.delete(k)); return next; } var Recoil_CopyOnWrite = { setByAddingToSet, setByDeletingFromSet, mapBySettingInMap, mapByUpdatingInMap, mapByDeletingFromMap, mapByDeletingMultipleFromMap }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ function sprintf(format, ...args) { let index = 0; return format.replace(/%s/g, () => String(args[index++])); } var sprintf_1 = sprintf; function expectationViolation(format, ...args) { { const message = sprintf_1.call(null, format, ...args); const error = new Error(message); error.name = 'Expectation Violation'; console.error(error); } } var expectationViolation_1 = expectationViolation; // @oss-only var Recoil_expectationViolation = expectationViolation_1; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ // eslint-disable-next-line no-unused-vars class AbstractRecoilValue { constructor(newKey) { _defineProperty(this, "key", void 0); this.key = newKey; } } class RecoilState extends AbstractRecoilValue {} class RecoilValueReadOnly extends AbstractRecoilValue {} function isRecoilValue(x) { return x instanceof RecoilState || x instanceof RecoilValueReadOnly; } var Recoil_RecoilValue = { AbstractRecoilValue, RecoilState, RecoilValueReadOnly, isRecoilValue }; var Recoil_RecoilValue_1 = Recoil_RecoilValue.AbstractRecoilValue; var Recoil_RecoilValue_2 = Recoil_RecoilValue.RecoilState; var Recoil_RecoilValue_3 = Recoil_RecoilValue.RecoilValueReadOnly; var Recoil_RecoilValue_4 = Recoil_RecoilValue.isRecoilValue; var Recoil_RecoilValue$1 = /*#__PURE__*/Object.freeze({ __proto__: null, AbstractRecoilValue: Recoil_RecoilValue_1, RecoilState: Recoil_RecoilValue_2, RecoilValueReadOnly: Recoil_RecoilValue_3, isRecoilValue: Recoil_RecoilValue_4 }); class DefaultValue {} const DEFAULT_VALUE = new DefaultValue(); class RecoilValueNotReady extends Error { constructor(key) { super(`Tried to set the value of Recoil selector ${key} using an updater function, but it is an async selector in a pending or error state; this is not supported.`); } } // flowlint-next-line unclear-type:off const nodes = new Map(); // flowlint-next-line unclear-type:off const recoilValues = new Map(); /* eslint-disable no-redeclare */ function registerNode(node) { if (nodes.has(node.key)) { const message = `Duplicate atom key "${node.key}". This is a FATAL ERROR in production. But it is safe to ignore this warning if it occurred because of hot module replacement.`; // TODO Need to figure out if there is a standard/open-source equivalent to see if hot module replacement is happening: // prettier-ignore // @fb-only: if (__DEV__) { // @fb-only: const isAcceptingUpdate = require('__debug').isAcceptingUpdate; // prettier-ignore // @fb-only: if (typeof isAcceptingUpdate !== 'function' || !isAcceptingUpdate()) { // @fb-only: expectationViolation(message, 'recoil'); // @fb-only: } // prettier-ignore // @fb-only: } else { // @fb-only: recoverableViolation(message, 'recoil'); // @fb-only: } console.warn(message); // @oss-only } nodes.set(node.key, node); const recoilValue = node.set == null ? new Recoil_RecoilValue$1.RecoilValueReadOnly(node.key) : new Recoil_RecoilValue$1.RecoilState(node.key); recoilValues.set(node.key, recoilValue); return recoilValue; } /* eslint-enable no-redeclare */ class NodeMissingError extends Error {} // flowlint-next-line unclear-type:off function getNode(key) { const node = nodes.get(key); if (node == null) { throw new NodeMissingError(`Missing definition for RecoilValue: "${key}""`); } return node; } // flowlint-next-line unclear-type:off function getNodeMaybe(key) { return nodes.get(key); } var Recoil_Node = { nodes, recoilValues, registerNode, getNode, getNodeMaybe, NodeMissingError, DefaultValue, DEFAULT_VALUE, RecoilValueNotReady }; const { mapByDeletingFromMap: mapByDeletingFromMap$1, mapBySettingInMap: mapBySettingInMap$1, setByAddingToSet: setByAddingToSet$1 } = Recoil_CopyOnWrite; const { getNode: getNode$1, getNodeMaybe: getNodeMaybe$1 } = Recoil_Node; // flowlint-next-line unclear-type:off const emptySet = Object.freeze(new Set()); class ReadOnlyRecoilValueError extends Error {} // Get the current value loadable of a node and update the state. // Update dependencies and subscriptions for selectors. // Update saved value validation for atoms. function getNodeLoadable(store, state, key) { return getNode$1(key).get(store, state); } // Peek at the current value loadable for a node without any evaluation or state change function peekNodeLoadable(store, state, key) { return getNode$1(key).peek(store, state); } // Write value directly to state bypassing the Node interface as the node // definitions may not have been loaded yet when processing the initial snapshot. function setUnvalidatedAtomValue_DEPRECATED(state, key, newValue) { var _node$invalidate; const node = getNodeMaybe$1(key); node === null || node === void 0 ? void 0 : (_node$invalidate = node.invalidate) === null || _node$invalidate === void 0 ? void 0 : _node$invalidate.call(node, state); return { ...state, atomValues: mapByDeletingFromMap$1(state.atomValues, key), nonvalidatedAtoms: mapBySettingInMap$1(state.nonvalidatedAtoms, key, newValue), dirtyAtoms: setByAddingToSet$1(state.dirtyAtoms, key) }; } // Return the discovered dependencies and values to be written by setting // a node value. (Multiple values may be written due to selectors getting to // set upstreams; deps may be discovered because of reads in updater functions.) function setNodeValue(store, state, key, newValue) { const node = getNode$1(key); if (node.set == null) { throw new ReadOnlyRecoilValueError(`Attempt to set read-only RecoilValue: ${key}`); } return node.set(store, state, newValue); } function cleanUpNode(store, key) { const node = getNode$1(key); node.cleanUp(store); } // Find all of the recursively dependent nodes function getDownstreamNodes(store, state, keys) { const visitedNodes = new Set(); const visitingNodes = Array.from(keys); const graph = store.getGraph(state.version); for (let key = visitingNodes.pop(); key; key = visitingNodes.pop()) { var _graph$nodeToNodeSubs; visitedNodes.add(key); const subscribedNodes = (_graph$nodeToNodeSubs = graph.nodeToNodeSubscriptions.get(key)) !== null && _graph$nodeToNodeSubs !== void 0 ? _graph$nodeToNodeSubs : emptySet; for (const downstreamNode of subscribedNodes) { if (!visitedNodes.has(downstreamNode)) { visitingNodes.push(downstreamNode); } } } return visitedNodes; } var Recoil_FunctionalCore = { getNodeLoadable, peekNodeLoadable, setNodeValue, cleanUpNode, setUnvalidatedAtomValue_DEPRECATED, getDownstreamNodes }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ /** * Returns a set containing all of the values from the first set that are not * present in any of the subsequent sets. * * Note: this is written procedurally (i.e., without filterSet) for performant * use in tight loops. */ function differenceSets(set, ...setsWithValuesToRemove) { const ret = new Set(); FIRST: for (const value of set) { for (const otherSet of setsWithValuesToRemove) { if (otherSet.has(value)) { continue FIRST; } } ret.add(value); } return ret; } var Recoil_differenceSets = differenceSets; function graph() { return { nodeDeps: new Map(), nodeToNodeSubscriptions: new Map() }; } function cloneGraph(graph) { return { nodeDeps: Recoil_mapMap(graph.nodeDeps, s => new Set(s)), nodeToNodeSubscriptions: Recoil_mapMap(graph.nodeToNodeSubscriptions, s => new Set(s)) }; } // Note that this overwrites the deps of existing nodes, rather than unioning // the new deps with the old deps. function mergeDependencyMapIntoGraph(deps, graph, // If olderGraph is given then we will not overwrite changes made to the given // graph compared with olderGraph: olderGraph) { const { nodeDeps, nodeToNodeSubscriptions } = graph; deps.forEach((upstreams, downstream) => { const existingUpstreams = nodeDeps.get(downstream); if (existingUpstreams && olderGraph && existingUpstreams !== olderGraph.nodeDeps.get(downstream)) { return; } // Update nodeDeps: nodeDeps.set(downstream, new Set(upstreams)); // Add new deps to nodeToNodeSubscriptions: const addedUpstreams = existingUpstreams == null ? upstreams : Recoil_differenceSets(upstreams, existingUpstreams); addedUpstreams.forEach(upstream => { if (!nodeToNodeSubscriptions.has(upstream)) { nodeToNodeSubscriptions.set(upstream, new Set()); } const existing = Recoil_nullthrows(nodeToNodeSubscriptions.get(upstream)); existing.add(downstream); }); // Remove removed deps from nodeToNodeSubscriptions: if (existingUpstreams) { const removedUpstreams = Recoil_differenceSets(existingUpstreams, upstreams); removedUpstreams.forEach(upstream => { if (!nodeToNodeSubscriptions.has(upstream)) { return; } const existing = Recoil_nullthrows(nodeToNodeSubscriptions.get(upstream)); existing.delete(downstream); if (existing.size === 0) { nodeToNodeSubscriptions.delete(upstream); } }); } }); } function saveDependencyMapToStore(dependencyMap, store, version) { var _storeState$nextTree, _storeState$previousT, _storeState$previousT2, _storeState$previousT3; const storeState = store.getState(); if (!(version === storeState.currentTree.version || version === ((_storeState$nextTree = storeState.nextTree) === null || _storeState$nextTree === void 0 ? void 0 : _storeState$nextTree.version) || version === ((_storeState$previousT = storeState.previousTree) === null || _storeState$previousT === void 0 ? void 0 : _storeState$previousT.version))) { Recoil_recoverableViolation('Tried to save dependencies to a discarded tree'); } // Merge the dependencies discovered into the store's dependency map // for the version that was read: const graph = store.getGraph(version); mergeDependencyMapIntoGraph(dependencyMap, graph); // If this version is not the latest version, also write these dependencies // into later versions if they don't already have their own: if (version === ((_storeState$previousT2 = storeState.previousTree) === null || _storeState$previousT2 === void 0 ? void 0 : _storeState$previousT2.version)) { const currentGraph = store.getGraph(storeState.currentTree.version); mergeDependencyMapIntoGraph(dependencyMap, currentGraph, graph); } if (version === ((_storeState$previousT3 = storeState.previousTree) === null || _storeState$previousT3 === void 0 ? void 0 : _storeState$previousT3.version) || version === storeState.currentTree.version) { var _storeState$nextTree2; const nextVersion = (_storeState$nextTree2 = storeState.nextTree) === null || _storeState$nextTree2 === void 0 ? void 0 : _storeState$nextTree2.version; if (nextVersion !== undefined) { const nextGraph = store.getGraph(nextVersion); mergeDependencyMapIntoGraph(dependencyMap, nextGraph, graph); } } } function mergeDepsIntoDependencyMap(from, into) { from.forEach((upstreamDeps, downstreamNode) => { if (!into.has(downstreamNode)) { into.set(downstreamNode, new Set()); } const deps = Recoil_nullthrows(into.get(downstreamNode)); upstreamDeps.forEach(dep => deps.add(dep)); }); } function addToDependencyMap(downstream, upstream, dependencyMap) { if (!dependencyMap.has(downstream)) { dependencyMap.set(downstream, new Set()); } Recoil_nullthrows(dependencyMap.get(downstream)).add(upstream); } var Recoil_Graph = { addToDependencyMap, cloneGraph, graph, mergeDepsIntoDependencyMap, saveDependencyMapToStore }; const { getDownstreamNodes: getDownstreamNodes$1, getNodeLoadable: getNodeLoadable$1, setNodeValue: setNodeValue$1 } = Recoil_FunctionalCore; const { saveDependencyMapToStore: saveDependencyMapToStore$1 } = Recoil_Graph; const { getNodeMaybe: getNodeMaybe$2 } = Recoil_Node; const { DefaultValue: DefaultValue$1, RecoilValueNotReady: RecoilValueNotReady$1 } = Recoil_Node; const { AbstractRecoilValue: AbstractRecoilValue$1, RecoilState: RecoilState$1, RecoilValueReadOnly: RecoilValueReadOnly$1, isRecoilValue: isRecoilValue$1 } = Recoil_RecoilValue$1; function getRecoilValueAsLoadable(store, { key }, treeState = store.getState().currentTree) { var _storeState$nextTree, _storeState$previousT; // Reading from an older tree can cause bugs because the dependencies that we // discover during the read are lost. const storeState = store.getState(); if (!(treeState.version === storeState.currentTree.version || treeState.version === ((_storeState$nextTree = storeState.nextTree) === null || _storeState$nextTree === void 0 ? void 0 : _storeState$nextTree.version) || treeState.version === ((_storeState$previousT = storeState.previousTree) === null || _storeState$previousT === void 0 ? void 0 : _storeState$previousT.version))) { Recoil_recoverableViolation('Tried to read from a discarded tree'); } const [dependencyMap, loadable] = getNodeLoadable$1(store, treeState, key); if (!Recoil_gkx_1('recoil_async_selector_refactor')) { /** * In selector_NEW, we take care of updating state deps within the selector */ saveDependencyMapToStore$1(dependencyMap, store, treeState.version); } return loadable; } function applyAtomValueWrites(atomValues, writes) { const result = Recoil_mapMap(atomValues, v => v); writes.forEach((v, k) => { if (v.state === 'hasValue' && v.contents instanceof DefaultValue$1) { result.delete(k); } else { result.set(k, v); } }); return result; } function valueFromValueOrUpdater(store, state, { key }, valueOrUpdater) { if (typeof valueOrUpdater === 'function') { // Updater form: pass in the current value. Throw if the current value // is unavailable (namely when updating an async selector that's // pending or errored): // NOTE: This will evaluate node, but not update state with node subscriptions! const current = getNodeLoadable$1(store, state, key)[1]; if (current.state === 'loading') { throw new RecoilValueNotReady$1(key); } else if (current.state === 'hasError') { throw current.contents; } // T itself may be a function, so our refinement is not sufficient: return valueOrUpdater(current.contents); // flowlint-line unclear-type:off } else { return valueOrUpdater; } } function applyAction(store, state, action) { if (action.type === 'set') { const { recoilValue, valueOrUpdater } = action; const newValue = valueFromValueOrUpdater(store, state, recoilValue, valueOrUpdater); const [depMap, writes] = setNodeValue$1(store, state, recoilValue.key, newValue); saveDependencyMapToStore$1(depMap, store, state.version); for (const [key, loadable] of writes.entries()) { writeLoadableToTreeState(state, key, loadable); } } else if (action.type === 'setLoadable') { const { recoilValue: { key }, loadable } = action; writeLoadableToTreeState(state, key, loadable); } else if (action.type === 'markModified') { const { recoilValue: { key } } = action; state.dirtyAtoms.add(key); } else if (action.type === 'setUnvalidated') { var _node$invalidate; // Write value directly to state bypassing the Node interface as the node // definitions may not have been loaded yet when processing the initial snapshot. const { recoilValue: { key }, unvalidatedValue } = action; const node = getNodeMaybe$2(key); node === null || node === void 0 ? void 0 : (_node$invalidate = node.invalidate) === null || _node$invalidate === void 0 ? void 0 : _node$invalidate.call(node, state); state.atomValues.delete(key); state.nonvalidatedAtoms.set(key, unvalidatedValue); state.dirtyAtoms.add(key); } else { Recoil_recoverableViolation(`Unknown action ${action.type}`); } } function writeLoadableToTreeState(state, key, loadable) { if (loadable.state === 'hasValue' && loadable.contents instanceof DefaultValue$1) { state.atomValues.delete(key); } else { state.atomValues.set(key, loadable); } state.dirtyAtoms.add(key); state.nonvalidatedAtoms.delete(key); } function applyActionsToStore(store, actions) { store.replaceState(state => { const newState = copyTreeState(state); for (const action of actions) { applyAction(store, newState, action); } invalidateDownstreams(store, newState); return newState; }); } function queueOrPerformStateUpdate(store, action, key, message) { if (batchStack.length) { const actionsByStore = batchStack[batchStack.length - 1]; let actions = actionsByStore.get(store); if (!actions) { actionsByStore.set(store, actions = []); } actions.push(action); } else { Recoil_Tracing.trace(message, key, () => applyActionsToStore(store, [action])); } } const batchStack = []; function batchStart() { const actionsByStore = new Map(); batchStack.push(actionsByStore); return () => { for (const [store, actions] of actionsByStore) { Recoil_Tracing.trace('Recoil batched updates', '-', () => applyActionsToStore(store, actions)); } const popped = batchStack.pop(); if (popped !== actionsByStore) { Recoil_recoverableViolation('Incorrect order of batch popping'); } }; } function copyTreeState(state) { return { ...state, atomValues: new Map(state.atomValues), nonvalidatedAtoms: new Map(state.nonvalidatedAtoms), dirtyAtoms: new Set(state.dirtyAtoms) }; } function invalidateDownstreams(store, state) { // Inform any nodes that were changed or downstream of changes so that they // can clear out any caches as needed due to the update: const downstreams = getDownstreamNodes$1(store, state, state.dirtyAtoms); for (const key of downstreams) { var _getNodeMaybe, _getNodeMaybe$invalid; (_getNodeMaybe = getNodeMaybe$2(key)) === null || _getNodeMaybe === void 0 ? void 0 : (_getNodeMaybe$invalid = _getNodeMaybe.invalidate) === null || _getNodeMaybe$invalid === void 0 ? void 0 : _getNodeMaybe$invalid.call(_getNodeMaybe, state); } } function setRecoilValue(store, recoilValue, valueOrUpdater) { queueOrPerformStateUpdate(store, { type: 'set', recoilValue, valueOrUpdater }, recoilValue.key, 'set Recoil value'); } function setRecoilValueLoadable(store, recoilValue, loadable) { if (loadable instanceof DefaultValue$1) { return setRecoilValue(store, recoilValue, loadable); } queueOrPerformStateUpdate(store, { type: 'setLoadable', recoilValue, loadable }, recoilValue.key, 'set Recoil value'); } function markRecoilValueModified(store, recoilValue) { queueOrPerformStateUpdate(store, { type: 'markModified', recoilValue }, recoilValue.key, 'mark RecoilValue modified'); } function setUnvalidatedRecoilValue(store, recoilValue, unvalidatedValue) { queueOrPerformStateUpdate(store, { type: 'setUnvalidated', recoilValue, unvalidatedValue }, recoilValue.key, 'set Recoil value'); } let subscriptionID = 0; function subscribeToRecoilValue(store, { key }, callback, componentDebugName = null) { const subID = subscriptionID++; const storeState = store.getState(); if (!storeState.nodeToComponentSubscriptions.has(key)) { storeState.nodeToComponentSubscriptions.set(key, new Map()); } Recoil_nullthrows(storeState.nodeToComponentSubscriptions.get(key)).set(subID, [componentDebugName !== null && componentDebugName !== void 0 ? componentDebugName : '<not captured>', callback]); return { release: () => { const storeState = store.getState(); const subs = storeState.nodeToComponentSubscriptions.get(key); if (subs === undefined || !subs.has(subID)) { Recoil_recoverableViolation(`Subscription missing at release time for atom ${key}. This is a bug in Recoil.`); return; } subs.delete(subID); if (subs.size === 0) { storeState.nodeToComponentSubscriptions.delete(key); } } }; } var Recoil_RecoilValueInterface = { RecoilValueReadOnly: RecoilValueReadOnly$1, AbstractRecoilValue: AbstractRecoilValue$1, RecoilState: RecoilState$1, getRecoilValueAsLoadable, setRecoilValue, setRecoilValueLoadable, markRecoilValueModified, setUnvalidatedRecoilValue, subscribeToRecoilValue, isRecoilValue: isRecoilValue$1, applyAtomValueWrites, // TODO Remove export when deprecating initialStoreState_DEPRECATED in RecoilRoot batchStart, invalidateDownstreams_FOR_TESTING: invalidateDownstreams }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format * * This is to export esstiential functions from react-dom * for our web build */ const { unstable_batchedUpdates } = reactDom; // @oss-only // @fb-only: const {unstable_batchedUpdates} = require('ReactDOMComet'); var Recoil_ReactBatchedUpdates = { unstable_batchedUpdates }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ const { batchStart: batchStart$1 } = Recoil_RecoilValueInterface; const { unstable_batchedUpdates: unstable_batchedUpdates$1 } = Recoil_ReactBatchedUpdates; let batcher = unstable_batchedUpdates$1; // flowlint-next-line unclear-type:off /** * Sets the provided batcher function as the batcher function used by Recoil. * * Set the batcher to a custom batcher for your renderer, * if you use a renderer other than React DOM or React Native. */ const setBatcher = newBatcher => { batcher = newBatcher; }; /** * Returns the current batcher function. */ const getBatcher = () => batcher; /** * Calls the current batcher function and passes the * provided callback function. */ const batchUpdates = callback => { batcher(() => { let batchEnd = () => undefined; try { batchEnd = batchStart$1(); callback(); } finally { batchEnd(); } }); }; var Recoil_Batching = { getBatcher, setBatcher, batchUpdates }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ function enqueueExecution(s, f) { f(); } var Recoil_Queue = { enqueueExecution }; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ /** * Combines multiple Iterables into a single Iterable. * Traverses the input Iterables in the order provided and maintains the order * of their elements. * * Example: * ``` * const r = Array.from(concatIterables(['a', 'b'], ['c'], ['d', 'e', 'f'])); * r == ['a', 'b', 'c', 'd', 'e', 'f']; * ``` */ function* concatIterables(iters) { for (const iter of iters) { for (const val of iter) { yield val; } } } var Recoil_concatIterables = concatIterables; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ /** * Creates a new iterable whose output is generated by passing the input * iterable's values through the filter function. */ function* filterIterable(iterable, predicate) { // Use generator to create iterable/iterator let index = 0; for (const value of iterable) { if (predicate(value, index++)) { yield value; } } } var Recoil_filterIterable = filterIterable; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ /** * Creates a new iterable whose output is generated by passing the input * iterable's values through the mapper function. */ function mapIterable(iterable, callback) { // Use generator to create iterable/iterator return function* () { let index = 0; for (const value of iterable) { yield callback(value, index++); } }(); } var Recoil_mapIterable = mapIterable; const { graph: graph$1 } = Recoil_Graph; // flowlint-next-line unclear-type:off let nextTreeStateVersion = 0; const getNextTreeStateVersion = () => nextTreeStateVersion++; function makeEmptyTreeState() { const version = getNextTreeStateVersion(); return { version, stateID: version, transactionMetadata: {}, dirtyAtoms: new Set(), atomValues: new Map(), nonvalidatedAtoms: new Map() }; } function makeEmptyStoreState() { const currentTree = makeEmptyTreeState(); return { currentTree, nextTree: null, previousTree: null, knownAtoms: new Set(), knownSelectors: new Set(), transactionSubscriptions: new Map(), nodeTransactionSubscriptions: new Map(), nodeToComponentSubscriptions: new Map(), queuedComponentCallbacks_DEPRECATED: [], suspendedComponentResolvers: new Set(), graphsByVersion: new Map().set(currentTree.version, graph$1()), versionsUsedByComponent: new Map() }; } var Recoil_State = { makeEmptyTreeState, makeEmptyStoreState, getNextTreeStateVersion }; const { batchUpdates: batchUpdates$1 } = Recoil_Batching; const { getDownstreamNodes: getDownstreamNodes$2, peekNodeLoadable: peekNodeLoadable$1 } = Recoil_FunctionalCore; const { graph: graph$2 } = Recoil_Graph; const { DEFAULT_VALUE: DEFAULT_VALUE$1, recoilValues: recoilValues$1 } = Recoil_Node; const { getRecoilValueAsLoadable: getRecoilValueAsLoadable$1, setRecoilValue: setRecoilValue$1 } = Recoil_RecoilValueInterface; const { getNextTreeStateVersion: getNextTreeStateVersion$1, makeEmptyStoreState: makeEmptyStoreState$1 } = Recoil_State; // Opaque at this surface because it's part of the public API from here. function recoilValuesForKeys(keys) { return Recoil_mapIterable(keys, key => Recoil_nullthrows(recoilValues$1.get(key))); } // A "Snapshot" is "read-only" and captures a specific set of values of atoms. // However, the data-flow-graph and selector values may evolve as selector // evaluation functions are executed and async selectors resolve. class Snapshot { constructor(storeState) { _defineProperty(this, "_store", void 0); _defineProperty(this, "getLoadable", recoilValue => // $FlowFixMe[escaped-generic] getRecoilValueAsLoadable$1(this._store, recoilValue)); _defineProperty(this, "getPromise", recoilValue => // $FlowFixMe[escaped-generic] this.getLoadable(recoilValue).toPromise()); _defineProperty(this, "getNodes_UNSTABLE", opt => { // TODO Deal with modified selectors if ((opt === null || opt === void 0 ? void 0 : opt.isModified) === true) { if ((opt === null || opt === void 0 ? void 0 : opt.isInitialized) === false) { return []; } const state = this._store.getState().currentTree; return recoilValuesForKeys(state.dirtyAtoms); } const knownAtoms = this._store.getState().knownAtoms; const knownSelectors = this._store.getState().knownSelectors; return (opt === null || opt === void 0 ? void 0 : opt.isInitialized) == null ? recoilValues$1.values() : opt.isInitialized === true ? recoilValuesForKeys(Recoil_concatIterables([this._store.getState().knownAtoms, this._store.getState().knownSelectors])) : Recoil_filterIterable(recoilValues$1.values(), ({ key }) => !knownAtoms.has(key) && !knownSelectors.has(key)); }); _defineProperty(this, "getDeps_UNSTABLE", recoilValue => { this.getLoadable(recoilValue); // Evaluate node to ensure deps are up-to-date const deps = this._store.getGraph(this._store.getState().currentTree.version).nodeDeps.get(recoilValue.key); return recoilValuesForKeys(deps !== null && deps !== void 0 ? deps : []); }); _defineProperty(this, "getSubscribers_UNSTABLE", ({ key }) => { const state = this._store.getState().currentTree; const downstreamNodes = Recoil_filterIterable(getDownstreamNodes$2(this._store, state, new Set([key])), nodeKey => nodeKey !== key); return { nodes: recoilValuesForKeys(downstreamNodes) }; }); _defineProperty(this, "getInfo_UNSTABLE", recoilValue => { var _graph$nodeDeps$get; const { key } = recoilValue; const state = this._store.getState().currentTree; const graph = this._store.getGraph(state.version); const type = this._store.getState().knownAtoms.has(key) ? 'atom' : this._store.getState().knownSelectors.has(key) ? 'selector' : undefined; return { loadable: peekNodeLoadable$1(this._store, state, key), isActive: this._store.getState().knownAtoms.has(key) || this._store.getState().knownSelectors.has(key), isSet: type === 'selector' ? false : state.atomValues.has(key), isModified: state.dirtyAtoms.has(key), type, // Don't use this.getDeps() as it will evaluate the node and we are only peeking deps: recoilValuesForKeys((_graph$nodeDeps$get = graph.nodeDeps.get(key)) !== null && _graph$nodeDeps$get !== void 0 ? _graph$nodeDeps$get : []), subscribers: this.getSubscribers_UNSTABLE(recoilValue) }; }); _defineProperty(this, "map", mapper => { const mutableSnapshot = new MutableSnapshot(this); mapper(mutableSnapshot); // if removing batchUpdates from `set` add it here return cloneSnapshot(mutableSnapshot.getStore_INTERNAL()); }); _defineProperty(this, "asyncMap", async mapper => { const mutableSnapshot = new MutableSnapshot(this); await mapper(mutableSnapshot); return cloneSnapshot(mutableSnapshot.getStore_INTERNAL()); }); this._store = { getState: () => storeState, replaceState: replacer => { storeState.currentTree = replacer(storeState.currentTree); // no batching so nextTree is never active }, getGraph: version => { const graphs = storeState.graphsByVersion; if (graphs.has(version)) { return Recoil_nullthrows(graphs.get(version)); } const newGraph = graph$2(); graphs.set(version, newGraph); return newGraph; }, subscribeToTransactions: () => ({ release: () => {} }), addTransactionMetadata: () => { throw new Error('Cannot subscribe to Snapshots'); } }; } getStore_INTERNAL() { return this._store; } getID() { return this.getID_INTERNAL(); } getID_INTERNAL() { return this._store.getState().currentTree.stateID; } } function cloneStoreState(store, treeState, bumpVersion = false) { const storeState = store.getState(); const version = bumpVersion ? getNextTreeStateVersion$1() : treeState.version; return { currentTree: bumpVersion ? { // TODO snapshots shouldn't really have versions because a new version number // is always assigned when the snapshot is gone to. version, stateID: version, transactionMetadata: { ...treeState.transactionMetadata }, dirtyAtoms: new Set(treeState.dirtyAtoms), atomValues: new Map(treeState.atomValues), nonvalidatedAtoms: new Map(treeState.nonvalidatedAtoms) } : treeState, nextTree: null, previousTree: null, knownAtoms: new Set(storeState.knownAtoms), // FIXME here's a copy knownSelectors: new Set(storeState.knownSelectors), // FIXME here's a copy transactionSubscriptions: new Map(), nodeTransactionSubscriptions: new Map(), nodeToComponentSubscriptions: new Map(), queuedComponentCallbacks_DEPRECATED: [], suspendedComponentResolvers: new Set(), graphsByVersion: new Map().set(version, store.getGraph(treeState.version)), versionsUsedByComponent: new Map() }; } // Factory to build a fresh snapshot function freshSnapshot(initializeState) { const snapshot = new Snapshot(makeEmptyStoreState$1()); return initializeState != null ? snapshot.map(initializeState) : snapshot; } // Factory to clone a snapahot state function cloneSnapshot(store, version = 'current') { const storeState = store.getState(); const treeState = version === 'current' ? storeState.currentTree : Recoil_nullthrows(storeState.previousTree); return new Snapshot(cloneStoreState(store, treeState)); } class MutableSnapshot extends Snapshot { constructor(snapshot) { super(cloneStoreState(snapshot.getStore_INTERNAL(), snapshot.getStore_INTERNAL().getState().currentTree, true)); _defineProperty(this, "set", (recoilState, newValueOrUpdater) => { const store = this.getStore_INTERNAL(); // This batchUpdates ensures this `set` is applied immediately and you can // read the written value after calling `set`. I would like to remove this // behavior and only batch in `Snapshot.map`, but this would be a breaking // change potentially. batchUpdates$1(() => { setRecoilValue$1(store, recoilState, newValueOrUpdater); }); }); _defineProperty(this, "reset", recoilState => // See note at `set` about batched updates. batchUpdates$1(() => setRecoilValue$1(this.getStore_INTERNAL(), recoilState, DEFAULT_VALUE$1))); } // We want to allow the methods to be destructured and used as accessors // eslint-disable-next-line fb-www/extra-arrow-initializer } var Recoil_Snapshot = { Snapshot, MutableSnapshot, freshSnapshot, cloneSnapshot }; var Recoil_Snapshot_1 = Recoil_Snapshot.Snapshot; var Recoil_Snapshot_2 = Recoil_Snapshot.MutableSnapshot; var Recoil_Snapshot_3 = Recoil_Snapshot.freshSnapshot; var Recoil_Snapshot_4 = Recoil_Snapshot.cloneSnapshot; var Recoil_Snapshot$1 = /*#__PURE__*/Object.freeze({ __proto__: null, Snapshot: Recoil_Snapshot_1, MutableSnapshot: Recoil_Snapshot_2, freshSnapshot: Recoil_Snapshot_3, cloneSnapshot: Recoil_Snapshot_4 }); /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails oncall+recoil * * @format */ function unionSets(...sets) { const result = new Set(); for (const set of sets) { for (const value of set) { result.add(value); } } return result; } var Recoil_unionSets = unionSets; const { useContext, useEffect, useMemo, useRef, useState } = react; // @fb-only: const RecoilusagelogEvent = require('RecoilusagelogEvent'); // @fb-only: const RecoilUsageLogFalcoEvent = require('RecoilUsageLogFalcoEvent'); // @fb-only: const URI = require('URI'); const { cleanUpNode: cleanUpNode$1, getDownstreamNodes: getDownstreamNodes$3, setNodeValue: setNodeValue$2, setUnvalidatedAtomValue_DEPRECATED: setUnvalidatedAtomValue_DEPRECATED$1 } = Recoil_FunctionalCore; const { graph: graph$3, saveDependencyMapToStore: saveDependencyMapToStore$2 } = Recoil_Graph; const { cloneGraph: cloneGraph$1 } = Recoil_Graph; const { applyAtomValueWrites: applyAtomValueWrites$1 } = Recoil_RecoilValueInterface; const { freshSnapshot: freshSnapshot$1 } = Recoil_Snapshot$1; const { getNextTreeStateVersion: getNextTreeStateVersion$2, makeEmptyStoreState: makeEmptyStoreState$2 } = Recoil_State; const { mapByDeletingMultipleFromMap: mapByDeletingMultipleFromMap$1 } = Recoil_CopyOnWrite; // @fb-only: const recoverableViolation = require('../util/Recoil_recoverableViolation'); // @fb-only: const gkx = require('gkx'); function notInAContext() { throw new Error('This component must be used inside a <RecoilRoot> component.'); } const defaultStore = Object.freeze({ getState: notInAContext, replaceState: notInAContext, getGraph: notInAContext, subscribeToTransactions: notInAContext, addTransactionMetadata: notInAContext }); let stateReplacerIsBeingExecuted = false; function startNextTreeIfNeeded(storeState) { if (stateReplacerIsBeingExecuted) { throw new Error('An atom update was triggered within the execution of a state updater function. State updater functions provided to Recoil must be pure functions.'); } if (storeState.nextTree === null) { const version = storeState.currentTree.version; const nextVersion = getNextTreeStateVersion$2(); storeState.nextTree = { ...storeState.currentTree, version: nextVersion, stateID: nextVersion, dirtyAtoms: new Set(), transactionMetadata: {} }; storeState.graphsByVersion.set(nextVersion, cloneGraph$1(Recoil_nullthrows(storeState.graphsByVersion.get(version)))); } } const AppContext = react.createContext({ current: defaultStore }); const useStoreRef = () => useContext(AppContext); const MutableSourceContext = react.createContext(null); // TODO T2710559282599660 const useRecoilMutableSource = () => useContext(MutableSourceContext); function sendEndOfBatchNotifications(store) { const storeState = store.getState(); const treeState = storeState.currentTree; // Inform transaction subscribers of the transaction: const dirtyAtoms = treeState.dirtyAtoms; if (dirtyAtoms.size) { // Execute Node-specific subscribers before global subscribers for (const [key, subscriptions] of storeState.nodeTransactionSubscriptions) { if (dirtyAtoms.has(key)) { for (const [_, subscription] of subscriptions) { subscription(store); } } } for (const [_, subscription] of storeState.transactionSubscriptions) { subscription(store); } // Components that are subscribed to the dirty atom: const dependentNodes = getDownstreamNodes$3(store, treeState, dirtyAtoms); for (const key of dependentNodes) { const comps = storeState.nodeToComponentSubscriptions.get(key); if (comps) { for (const [_subID, [_debugName, callback]] of comps) { callback(treeState); } } } // Wake all suspended components so the right one(s) can try to re-render. // We need to wake up components not just when some asynchronous selector // resolved, but also when changing synchronous values because this may cause // a selector to change from asynchronous to synchronous, in which case there // would be no follow-up asynchronous resolution to wake us up. // TODO OPTIMIZATION Only wake up related downstream components let nodeNames = '[available in dev build]'; { nodeNames = Array.from(dirtyAtoms).join(', '); } storeState.suspendedComponentResolvers.forEach(cb => Recoil_Tracing.trace('value became available, waking components', nodeNames, cb)); } // Special behavior ONLY invoked by useInterface. // FIXME delete queuedComponentCallbacks_DEPRECATED when deleting useInterface. storeState.queuedComponentCallbacks_DEPRECATED.forEach(cb => cb(treeState)); storeState.queuedComponentCallbacks_DEPRECATED.splice(0, storeState.queuedComponentCallbacks_DEPRECATED.length); } /* * The purpose of the Batcher is to observe when React batches end so that * Recoil state changes can be batched. Whenever Recoil state changes, we call * setState on the batcher. Then we wait for that change to be committed, which * signifies the end of the batch. That's when we respond to the Recoil change. */ function Batcher(props) { const storeRef = useStoreRef(); const [_, setState] = useState([]); props.setNotifyBatcherOfChange(() => setState({})); useEffect(() => { // enqueueExecution runs this function immediately; it is only used to // manipulate the order of useEffects during tests, since React seems to // call useEffect in an unpredictable order sometimes. Recoil_Queue.enqueueExecution('Batcher', () => { const storeState = storeRef.current.getState(); const { nextTree } = storeState; // Ignore commits that are not because of Recoil transactions -- namely, // because something above RecoilRoot re-rendered: if (nextTree === null) { return; } // nextTree is now committed -- note that copying and reset occurs when // a transaction begins, in startNextTreeIfNeeded: storeState.previousTree = storeState.currentTree; storeState.currentTree = nextTree; storeState.nextTree = null; sendEndOfBatchNotifications(storeRef.current); const discardedVersion = Recoil_nullthrows(storeState.pr