react-native-onyx
Version:
State management for React Native
416 lines (415 loc) • 17.7 kB
TypeScript
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, };