UNPKG

recoil

Version:

Recoil - A state management library for React

196 lines (169 loc) 7.7 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, LoadablePromise } from '../adt/Recoil_Loadable'; import type { CacheImplementation } from '../caches/Recoil_Cache'; import type { NodeCache, NodeCacheRoute } from '../caches/Recoil_NodeCache'; import type { DependencyMap } from '../core/Recoil_Graph'; import type { DefaultValue } from '../core/Recoil_Node'; import type { RecoilState, RecoilValue, RecoilValueReadOnly } from '../core/Recoil_RecoilValue'; import type { AtomValues, NodeKey, Store, TreeState } from '../core/Recoil_State'; const { loadableWithError, loadableWithPromise, loadableWithValue } = require('../adt/Recoil_Loadable'); const cacheMostRecent = require('../caches/Recoil_cacheMostRecent'); const cacheWithReferenceEquality = require('../caches/Recoil_cacheWithReferenceEquality'); const cacheWithValueEquality = require('../caches/Recoil_cacheWithValueEquality'); const nodeCacheMostRecent = require('../caches/Recoil_nodeCacheMostRecent'); const treeCacheReferenceEquality = require('../caches/Recoil_treeCacheReferenceEquality'); const treeCacheValueEquality = require('../caches/Recoil_treeCacheValueEquality'); const { getNodeLoadable, peekNodeLoadable, setNodeValue } = require('../core/Recoil_FunctionalCore'); const { saveDependencyMapToStore } = require('../core/Recoil_Graph'); const { DEFAULT_VALUE, RecoilValueNotReady, registerNode } = require('../core/Recoil_Node'); const { isRecoilValue } = require('../core/Recoil_RecoilValue'); const { AbstractRecoilValue } = require('../core/Recoil_RecoilValue'); const { setRecoilValueLoadable } = require('../core/Recoil_RecoilValueInterface'); const deepFreezeValue = require('../util/Recoil_deepFreezeValue'); const isPromise = require('../util/Recoil_isPromise'); const nullthrows = require('../util/Recoil_nullthrows'); const { startPerfBlock } = require('../util/Recoil_PerformanceTimings'); export type ValueOrUpdater<T> = T | DefaultValue | ((prevValue: T) => T | DefaultValue); export type GetRecoilValue = <T>(RecoilValue<T>) => T; export type SetRecoilState = <T>(RecoilState<T>, ValueOrUpdater<T>) => void; export type ResetRecoilState = <T>(RecoilState<T>) => void; type ReadOnlySelectorOptions<T> = $ReadOnly<{ key: string, get: ({ get: GetRecoilValue }) => Promise<T> | RecoilValue<T> | T, cacheImplementation_UNSTABLE?: CacheImplementation<Loadable<T>>, dangerouslyAllowMutability?: boolean, }>; type ReadWriteSelectorOptions<T> = $ReadOnly<{ ...ReadOnlySelectorOptions<T>, set: ({ set: SetRecoilState, get: GetRecoilValue, reset: ResetRecoilState, }, newValue: T | DefaultValue) => void, }>; export type DepValues = Map<NodeKey, Loadable<mixed>>; /** * 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, }; // flowlint-next-line unclear-type:off const emptySet: $ReadOnlySet<any> = Object.freeze(new Set()); 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;