UNPKG

react-native-onyx

Version:

State management for React Native

416 lines (415 loc) 17.7 kB
import type { Merge } from 'type-fest'; import type { BuiltIns } from 'type-fest/source/internal'; import type OnyxUtils from './OnyxUtils'; import type { WithOnyxInstance, WithOnyxState } from './withOnyx/types'; import type { OnyxMethod } from './OnyxUtils'; /** * Utility type that excludes `null` from the type `TValue`. */ type NonNull<TValue> = TValue extends null ? never : TValue; /** * Utility type that excludes `undefined` from the type `TValue`. */ type NonUndefined<TValue> = TValue extends undefined ? never : TValue; /** * Represents a deeply nested record. It maps keys to values, * and those values can either be of type `TValue` or further nested `DeepRecord` instances. */ type DeepRecord<TKey extends string | number | symbol, TValue> = { [key: string]: TValue | DeepRecord<TKey, TValue>; }; /** * Represents type options to configure all Onyx methods. * It's a combination of predefined options with user-provided options (CustomTypeOptions). * * The options are: * - `keys`: Represents a string union of all Onyx normal keys. * - `collectionKeys`: Represents a string union of all Onyx collection keys. * - `values`: Represents a Record where each key is an Onyx key and each value is its corresponding Onyx value type. * * The user-defined options (CustomTypeOptions) are merged into these predefined options. * In case of conflicting properties, the ones from CustomTypeOptions are prioritized. */ type TypeOptions = Merge<{ keys: string; collectionKeys: string; values: Record<string, unknown>; }, CustomTypeOptions>; /** * Represents the user-defined options to configure all Onyx methods. * * The developer can configure Onyx methods by augmenting this library and overriding CustomTypeOptions. * * @example * ```ts * // ONYXKEYS.ts * import {ValueOf} from 'type-fest'; * import { Account, Report } from './types'; * * const ONYXKEYS = { * ACCOUNT: 'account', * IS_SIDEBAR_LOADED: 'isSidebarLoaded', * * // Collection Keys * COLLECTION: { * REPORT: 'report_', * }, * } as const; * * type OnyxKeysMap = typeof ONYXKEYS; * type OnyxCollectionKey = ValueOf<OnyxKeysMap['COLLECTION']>; * type OnyxKey = DeepValueOf<Omit<OnyxKeysMap, 'COLLECTION'>>; * * type OnyxValues = { * [ONYXKEYS.ACCOUNT]: Account; * [ONYXKEYS.IS_SIDEBAR_LOADED]: boolean; * [ONYXKEYS.COLLECTION.REPORT]: Report; * }; * * export default ONYXKEYS; * export type {OnyxKey, OnyxCollectionKey, OnyxValues}; * * // global.d.ts * import {OnyxKey, OnyxCollectionKey, OnyxValues} from './ONYXKEYS'; * * declare module 'react-native-onyx' { * interface CustomTypeOptions { * keys: OnyxKey; * collectionKeys: OnyxCollectionKey; * values: OnyxValues; * } * } * ``` */ interface CustomTypeOptions { } /** * Represents a string union of all Onyx normal keys. */ type Key = TypeOptions['keys']; /** * Represents a string union of all Onyx collection keys. */ type CollectionKeyBase = TypeOptions['collectionKeys']; /** * Represents a literal string union of all Onyx collection keys. * It allows appending a string after each collection key e.g. `report_some-id`. */ type CollectionKey = `${CollectionKeyBase}${string}`; /** * Represents a string union of all Onyx normal and collection keys. */ type OnyxKey = Key | CollectionKey; /** * Represents a selector function type which operates based on the provided `TKey` and `ReturnType`. * * A `Selector` is a function that accepts a value, the withOnyx's internal state and returns a processed value. * This type accepts two type parameters: `TKey` and `TReturnType`. * * The type `TKey` extends `OnyxKey` and it is the key used to access a value in `KeyValueMapping`. * `TReturnType` is the type of the returned value from the selector function. */ type Selector<TKey extends OnyxKey, TOnyxProps, TReturnType> = (value: OnyxEntry<KeyValueMapping[TKey]>, state?: WithOnyxState<TOnyxProps>) => TReturnType; /** * Represents a single Onyx entry, that can be either `TOnyxValue` or `undefined` if it doesn't exist. * * It can be used to specify data retrieved from Onyx e.g. `withOnyx` HOC mappings. * * @example * ```ts * import Onyx, {OnyxEntry, withOnyx} from 'react-native-onyx'; * * type OnyxProps = { * userAccount: OnyxEntry<Account>; * }; * * type Props = OnyxProps & { * prop1: string; * }; * * function Component({prop1, userAccount}: Props) { * // ... * } * * export default withOnyx<Props, OnyxProps>({ * userAccount: { * key: ONYXKEYS.ACCOUNT, * }, * })(Component); * ``` */ type OnyxEntry<TOnyxValue> = TOnyxValue | undefined; /** * Represents an Onyx collection of entries, that can be either a record of `TOnyxValue`s or `undefined` if it is empty or doesn't exist. * * It can be used to specify collection data retrieved from Onyx e.g. `withOnyx` HOC mappings. * * @example * ```ts * import Onyx, {OnyxCollection, withOnyx} from 'react-native-onyx'; * * type OnyxProps = { * reports: OnyxCollection<Report>; * }; * * type Props = OnyxProps & { * prop1: string; * }; * * function Component({prop1, reports}: Props) { * // ... * } * * export default withOnyx<Props, OnyxProps>({ * reports: { * key: ONYXKEYS.COLLECTION.REPORT, * }, * })(Component); * ``` */ type OnyxCollection<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | undefined>>; /** * Represents a mapping of Onyx keys to values, where keys are either normal or collection Onyx keys * and values are the corresponding values in Onyx's state. * * For collection keys, `KeyValueMapping` allows any string to be appended * to the key (e.g., 'report_some-id', 'download_some-id'). * * The mapping is derived from the `values` property of the `TypeOptions` type. */ type KeyValueMapping = { [TKey in keyof TypeOptions['values'] as TKey extends CollectionKeyBase ? `${TKey}${string}` : TKey]: TypeOptions['values'][TKey]; }; /** * Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided. */ type OnyxValue<TKey extends OnyxKey> = string extends TKey ? unknown : TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>; /** Utility type to extract `TOnyxValue` from `OnyxCollection<TOnyxValue>` */ type ExtractOnyxCollectionValue<TOnyxCollection> = TOnyxCollection extends NonNullable<OnyxCollection<infer U>> ? U : never; type NonTransformableTypes = BuiltIns | ((...args: any[]) => unknown) | Map<unknown, unknown> | Set<unknown> | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | unknown[] | readonly unknown[]; /** * Create a type from another type with all keys and nested keys set to optional or null. * * @example * const settings: Settings = { * textEditor: { * fontSize: 14; * fontColor: '#000000'; * fontWeight: 400; * } * autosave: true; * }; * * const applySavedSettings = (savedSettings: NullishDeep<Settings>) => { * return {...settings, ...savedSettings}; * } * * settings = applySavedSettings({textEditor: {fontWeight: 500, fontColor: null}}); */ type NullishDeep<T> = T extends NonTransformableTypes ? T : T extends object ? NullishObjectDeep<T> : unknown; /** * Same as `NullishDeep`, but accepts only `object`s as inputs. Internal helper for `NullishDeep`. */ type NullishObjectDeep<ObjectType extends object> = { [KeyType in keyof ObjectType]?: NullishDeep<ObjectType[KeyType]> | null; }; /** * Represents a mapping between Onyx collection keys and their respective values. * * It helps to enforce that a Onyx collection key should not be without suffix (e.g. should always be of the form `${TKey}${string}`), * and to map each Onyx collection key with suffix to a value of type `TValue`. * * Also, the `TMap` type is inferred automatically in `mergeCollection()` method and represents * the object of collection keys/values specified in the second parameter of the method. */ type Collection<TKey extends CollectionKeyBase, TValue, TMap = never> = { [MapK in keyof TMap]: MapK extends `${TKey}${string}` ? MapK extends `${TKey}` ? never : TValue : never; }; /** Represents the base options used in `Onyx.connect()` method. */ type BaseConnectOptions = { /** If set to `false`, then the initial data will be only sent to the callback function if it changes. */ initWithStoredValues?: boolean; /** * If set to `false`, the connection won't be reused between other subscribers that are listening to the same Onyx key * with the same connect configurations. */ reuseConnection?: boolean; }; /** Represents the callback function used in `Onyx.connect()` method with a regular key. */ type DefaultConnectCallback<TKey extends OnyxKey> = (value: OnyxEntry<KeyValueMapping[TKey]>, key: TKey) => void; /** Represents the callback function used in `Onyx.connect()` method with a collection key. */ type CollectionConnectCallback<TKey extends OnyxKey> = (value: NonUndefined<OnyxCollection<KeyValueMapping[TKey]>>, key: TKey, sourceValue?: OnyxValue<TKey>) => void; /** Represents the options used in `Onyx.connect()` method with a regular key. */ type DefaultConnectOptions<TKey extends OnyxKey> = BaseConnectOptions & { /** The Onyx key to subscribe to. */ key: TKey; /** A function that will be called when the Onyx data we are subscribed changes. */ callback?: DefaultConnectCallback<TKey>; /** If set to `true`, it will return the entire collection to the callback as a single object. */ waitForCollectionCallback?: false; }; /** Represents the options used in `Onyx.connect()` method with a collection key. */ type CollectionConnectOptions<TKey extends OnyxKey> = BaseConnectOptions & { /** The Onyx key to subscribe to. */ key: TKey extends CollectionKeyBase ? TKey : never; /** A function that will be called when the Onyx data we are subscribed changes. */ callback?: CollectionConnectCallback<TKey>; /** If set to `true`, it will return the entire collection to the callback as a single object. */ waitForCollectionCallback: true; }; /** * Represents the options used in `Onyx.connect()` method. * The type is built from `DefaultConnectOptions`/`CollectionConnectOptions` depending on the `waitForCollectionCallback` property. * It includes two different forms, depending on whether we are waiting for a collection callback or not. * * If `waitForCollectionCallback` is `true`, it expects `key` to be a Onyx collection key and `callback` will be triggered with the whole collection * and will pass `value` as an `OnyxCollection`. * * If `waitForCollectionCallback` is `false` or not specified, the `key` can be any Onyx key and `callback` will be triggered with updates of each collection item * and will pass `value` as an `OnyxEntry`. */ type ConnectOptions<TKey extends OnyxKey> = DefaultConnectOptions<TKey> | CollectionConnectOptions<TKey>; /** Represents additional `Onyx.connect()` options used inside `withOnyx()` HOC. */ type WithOnyxConnectOptions<TKey extends OnyxKey> = ConnectOptions<TKey> & { /** The `withOnyx` class instance to be internally passed. */ withOnyxInstance: WithOnyxInstance; /** The name of the component's prop that is connected to the Onyx key. */ statePropertyName: string; /** The component's display name. */ displayName: string; /** * This will be used to subscribe to a subset of an Onyx key's data. * Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render * when the subset of data changes. Otherwise, any change of data on any property would normally * cause the component to re-render (and that can be expensive from a performance standpoint). */ selector?: Selector<TKey, unknown, unknown>; /** Determines if this key in this subscription is safe to be evicted. */ canEvict?: boolean; }; type Mapping<TKey extends OnyxKey> = WithOnyxConnectOptions<TKey> & { subscriptionID: number; }; /** * Represents a single Onyx input value, that can be either `TOnyxValue` or `null` if the key should be deleted. * This type is used for data passed to Onyx e.g. in `Onyx.merge` and `Onyx.set`. */ type OnyxInputValue<TOnyxValue> = TOnyxValue | null; /** * Represents an Onyx collection input, that can be either a record of `TOnyxValue`s or `null` if the key should be deleted. */ type OnyxCollectionInputValue<TOnyxValue> = OnyxInputValue<Record<string, TOnyxValue | null>>; /** * Represents an input value that can be passed to Onyx methods, that can be either `TOnyxValue` or `null`. * Setting a key to `null` will remove the key from the store. * `undefined` is not allowed for setting values, because it will have no effect on the data. */ type OnyxInput<TKey extends OnyxKey> = OnyxInputValue<NullishDeep<KeyValueMapping[TKey]>>; /** * Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`. * * It's very similar to `KeyValueMapping` but this type is used for inputs to Onyx * (set, merge, mergeCollection) and therefore accepts using `null` to remove a key from Onyx. */ type OnyxInputKeyValueMapping = { [TKey in OnyxKey]: OnyxInput<TKey>; }; /** * This represents the value that can be passed to `Onyx.set` and to `Onyx.update` with the method "SET" */ type OnyxSetInput<TKey extends OnyxKey> = OnyxInput<TKey>; /** * This represents the value that can be passed to `Onyx.multiSet` and to `Onyx.update` with the method "MULTI_SET" */ type OnyxMultiSetInput = Partial<OnyxInputKeyValueMapping>; /** * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE" */ type OnyxMergeInput<TKey extends OnyxKey> = OnyxInput<TKey>; /** * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE" */ type OnyxMergeCollectionInput<TKey extends OnyxKey, TMap = object> = Collection<TKey, NonNullable<OnyxInput<TKey>>, TMap>; type OnyxMethodMap = typeof OnyxUtils.METHOD; type OnyxMethodValueMap = { [OnyxUtils.METHOD.SET]: { key: OnyxKey; value: OnyxSetInput<OnyxKey>; }; [OnyxUtils.METHOD.MULTI_SET]: { key: OnyxKey; value: OnyxMultiSetInput; }; [OnyxUtils.METHOD.MERGE]: { key: OnyxKey; value: OnyxMergeInput<OnyxKey>; }; [OnyxUtils.METHOD.CLEAR]: { key: OnyxKey; value?: undefined; }; [OnyxUtils.METHOD.MERGE_COLLECTION]: { key: CollectionKeyBase; value: OnyxMergeCollectionInput<CollectionKeyBase>; }; [OnyxUtils.METHOD.SET_COLLECTION]: { key: CollectionKeyBase; value: OnyxMergeCollectionInput<CollectionKeyBase>; }; }; /** * OnyxUpdate type includes all onyx methods used in OnyxMethodValueMap. * If a new method is added to OnyxUtils.METHOD constant, it must be added to OnyxMethodValueMap type. * Otherwise it will show static type errors. */ type OnyxUpdate = { [Method in OnyxMethod]: { onyxMethod: Method; } & OnyxMethodValueMap[Method]; }[OnyxMethod]; /** * Represents the options used in `Onyx.init()` method. */ type InitOptions = { /** `ONYXKEYS` constants object */ keys?: DeepRecord<string, OnyxKey>; /** initial data to set when `init()` and `clear()` is called */ initialKeyStates?: Partial<OnyxInputKeyValueMapping>; /** * This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged * as "safe" for removal. Any components subscribing to these keys must also implement a canEvict option. See the README for more info. */ evictableKeys?: OnyxKey[]; /** * Sets how many recent keys should we try to keep in cache * Setting this to 0 would practically mean no cache * We try to free cache when we connect to a safe eviction key */ maxCachedKeysCount?: number; /** * Auto synchronize storage events between multiple instances * of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop) */ shouldSyncMultipleInstances?: boolean; /** Enables debugging setState() calls to connected components */ debugSetState?: boolean; /** * If enabled it will use the performance API to measure the time taken by Onyx operations. * @default false */ enablePerformanceMetrics?: boolean; /** * Array of collection member IDs which updates will be ignored when using Onyx methods. * Additionally, any subscribers from these keys to won't receive any data from Onyx. */ skippableCollectionMemberIDs?: string[]; }; type GenericFunction = (...args: any[]) => any; /** * Represents a combination of Merge and Set operations that should be executed in Onyx */ type MixedOperationsQueue = { merge: OnyxInputKeyValueMapping; set: OnyxInputKeyValueMapping; }; export type { BaseConnectOptions, Collection, CollectionConnectCallback, CollectionConnectOptions, CollectionKey, CollectionKeyBase, ConnectOptions, CustomTypeOptions, DeepRecord, DefaultConnectCallback, DefaultConnectOptions, ExtractOnyxCollectionValue, GenericFunction, InitOptions, Key, KeyValueMapping, Mapping, NonNull, NonUndefined, OnyxInputKeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxMethod, OnyxMethodMap, OnyxUpdate, OnyxValue, Selector, WithOnyxConnectOptions, MixedOperationsQueue, };