recoil
Version:
Recoil - A state management library for React
1,643 lines (1,332 loc) • 189 kB
JavaScript
(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