UNPKG

recoil

Version:

Recoil - A state management library for React

218 lines (190 loc) 8.29 kB
/** * 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;