recoil
Version:
Recoil - A state management library for React
218 lines (190 loc) • 8.29 kB
Flow
/**
* 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.
*
* Return an atom whose state cannot vary independently but is derived from that
* of other atoms. Whenever its dependency atoms change, it will re-evaluate
* a function and pass along the result to any components or further selectors:
*
* const exampleSelector = selector({
* key: 'example',
* get: ({get}) => {
* const a = get(atomA);
* const b = get(atomB);
* return a + b;
* },
* });
*
* In this example, the value of exampleSelector will be the sum of atomA and atomB.
* This sum will be updated whenever either atomA or atomB changes. The value
* returned by the function will be deeply frozen.
*
* The function is only reevaluated if the dependencies change and the selector
* has a component subscribed to it (either directly or indirectly via other
* selectors). By default, function results are cached, so if the same values
* of the dependencies are seen again, the cached value will be returned instead
* of the function being reevaluated. The caching behavior can be overridden
* by providing the `cacheImplementation` option; this can be used to discard
* old values or to provide different equality semantics.
*
* If the provided function returns a Promise, it will cause the value of the
* atom to become unavailable until the promise resolves. This means that any
* components subscribed to the selector will suspend. If the promise is rejected,
* any subscribed components will throw the rejecting error during rendering.
*
* You can provide the `set` option to allow writing to the selector. This
* should be used sparingly; maintain a conceptual separation between independent
* state and derived values. The `set` function receives a function to set
* upstream RecoilValues which can accept a value or an updater function.
* The updater function provides parameters with the old value of the RecoilValue
* as well as a get() function to read other RecoilValues.
*
* const multiplierSelector = selector({
* key: 'multiplier',
* get: ({get}) => get(atomA) * 100,
* set: ({set, reset, get}, newValue) => set(atomA, newValue / 100),
* });
*
* @emails oncall+recoil
* @flow strict-local
* @format
*/
'use strict';
import type { Loadable } from '../adt/Recoil_Loadable';
import type { CachePolicy } from '../caches/Recoil_CachePolicy';
import type { NodeCacheRoute, TreeCacheImplementation } from '../caches/Recoil_TreeCacheImplementationType';
import type { DefaultValue } from '../core/Recoil_Node';
import type { RecoilState, RecoilValue, RecoilValueReadOnly } from '../core/Recoil_RecoilValue';
import type { RetainedBy } from '../core/Recoil_RetainedBy';
import type { Snapshot } from '../core/Recoil_Snapshot';
import type { AtomWrites, NodeKey, Store, TreeState } from '../core/Recoil_State';
import type { GetRecoilValue, ResetRecoilState, SetRecoilState } from './Recoil_callbackTypes';
const {
loadableWithError,
loadableWithPromise,
loadableWithValue
} = require('../adt/Recoil_Loadable');
const treeCacheFromPolicy = require('../caches/Recoil_treeCacheFromPolicy');
const {
getNodeLoadable,
peekNodeLoadable,
setNodeValue
} = require('../core/Recoil_FunctionalCore');
const {
saveDependencyMapToStore
} = require('../core/Recoil_Graph');
const {
DEFAULT_VALUE,
RecoilValueNotReady,
getConfigDeletionHandler,
getNode,
registerNode
} = require('../core/Recoil_Node');
const {
isRecoilValue
} = require('../core/Recoil_RecoilValue');
const {
AbstractRecoilValue
} = require('../core/Recoil_RecoilValue');
const {
markRecoilValueModified,
setRecoilValueLoadable
} = require('../core/Recoil_RecoilValueInterface');
const {
retainedByOptionWithDefault
} = require('../core/Recoil_Retention');
const {
cloneSnapshot
} = require('../core/Recoil_Snapshot');
const deepFreezeValue = require('../util/Recoil_deepFreezeValue');
const err = require('../util/Recoil_err');
const gkx = require('../util/Recoil_gkx');
const invariant = require('../util/Recoil_invariant');
const isPromise = require('../util/Recoil_isPromise');
const nullthrows = require('../util/Recoil_nullthrows');
const {
startPerfBlock
} = require('../util/Recoil_PerformanceTimings');
const recoverableViolation = require('../util/Recoil_recoverableViolation');
export type GetCallback = <Args: $ReadOnlyArray<mixed>, Return>(fn: ($ReadOnly<{
snapshot: Snapshot
}>) => (...Args) => Return) => (...Args) => Return;
type ReadOnlySelectorOptions<T> = $ReadOnly<{
key: string,
get: ({
get: GetRecoilValue,
getCallback: GetCallback,
}) => Promise<T> | RecoilValue<T> | T,
dangerouslyAllowMutability?: boolean,
retainedBy_UNSTABLE?: RetainedBy,
cachePolicy_UNSTABLE?: CachePolicy,
}>;
type ReadWriteSelectorOptions<T> = $ReadOnly<{ ...ReadOnlySelectorOptions<T>,
set: ({
set: SetRecoilState,
get: GetRecoilValue,
reset: ResetRecoilState,
}, newValue: T | DefaultValue) => void,
}>;
export type DepValues = Map<NodeKey, Loadable<mixed>>;
declare class Canceled {}
const CANCELED: Canceled = new Canceled();
/**
* An ExecutionId is an arbitrary ID that lets us distinguish executions from
* each other. This is necessary as we need a way of solving this problem:
* "given 3 async executions, only update state for the 'latest' execution when
* it finishes running regardless of when the other 2 finish". ExecutionIds
* provide a convenient way of identifying executions so that we can track and
* manage them over time.
*/
type ExecutionId = number;
/**
* ExecutionInfo is useful for managing async work and resolving race
* conditions. It keeps track of the following:
*
* 1. The dep values found so far for the latest running execution. This is
* useful for answering the question "given a new state, have any of the
* async execution's discovered dep values changed?"
* 2. The latest loadable, which holds the loadable of the latest execution.
* This is important because we need to return this loadable when the
* selector's result is requested and there is a pending async execution. We
* are essentially caching the latest loading loadable without using the
* actual selector cache so that we can avoid creating cache keys that use
* partial dependencies (we never want to cache based on partial
* dependencies).
* 3. The latest execution ID, which is needed to know whether or not an async
* execution is stale. At any point in time there may be any number of stale
* executions running, but there is only one 'latest' execution, which
* represents the execution that will make its way to the UI and make updates
* to global state when it finishes.
*/
type ExecutionInfo<T> = {
depValuesDiscoveredSoFarDuringAsyncWork: ?DepValues,
latestLoadable: ?Loadable<T>,
latestExecutionId: ?ExecutionId,
stateVersion: ?number,
}; // An object to hold the current state for loading dependencies for a particular
// execution of a selector. This is used for async selector handling to know
// which dependency was pending or if a user-promise was thrown. An object is
// used instead of just a variable with the loadingDepKey so that if the
// selector is async we can still access the current state in a promise chain
// by updating the object reference.
type LoadingDepsState = {
loadingDepKey: NodeKey | null,
loadingDepPromise: Promise<mixed> | null,
};
const dependencyStack = []; // for detecting circular dependencies.
const waitingStores: Map<ExecutionId, Set<Store>> = new Map();
/* eslint-disable no-redeclare */
declare function selector<T>(options: ReadOnlySelectorOptions<T>): RecoilValueReadOnly<T>;
declare function selector<T>(options: ReadWriteSelectorOptions<T>): RecoilState<T>;
const getNewExecutionId: () => ExecutionId = (() => {
let executionId = 0;
return () => executionId++;
})();
declare function getInitialExecutionInfo<T>(): ExecutionInfo<T>;
declare function selector<T>(options: ReadOnlySelectorOptions<T> | ReadWriteSelectorOptions<T>): RecoilValue<T>;
/* eslint-enable no-redeclare */
module.exports = selector;