From 3b6b49d457b2daa6b2bf7ade25608b50dcee8121 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 31 May 2024 10:35:00 +0200 Subject: [PATCH 1/3] fix: onyx input method types (deep nullish values) --- lib/Onyx.ts | 20 +++++++++--------- lib/OnyxUtils.ts | 3 +-- lib/types.ts | 48 +++++++++++++++++++++++++++++++++++-------- lib/withOnyx/types.ts | 4 ++-- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/lib/Onyx.ts b/lib/Onyx.ts index 45065165..59d89b99 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -8,18 +8,18 @@ import Storage from './storage'; import utils from './utils'; import DevTools from './DevTools'; import type { - Collection, CollectionKeyBase, ConnectOptions, InitOptions, KeyValueMapping, Mapping, - NonUndefined, NullableKeyValueMapping, - NullishDeep, OnyxCollection, - OnyxEntry, OnyxKey, + OnyxMergeCollectionInput, + OnyxMergeInput, + OnyxMultiSetInput, + OnyxSetInput, OnyxUpdate, OnyxValue, } from './types'; @@ -209,7 +209,7 @@ function disconnect(connectionID: number, keyToRemoveFromEvictionBlocklist?: Ony * @param key ONYXKEY to set * @param value value to store */ -function set(key: TKey, value: NonUndefined>): Promise { +function set(key: TKey, value: OnyxSetInput): Promise { // When we use Onyx.set to set a key we want to clear the current delta changes from Onyx.merge that were queued // before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes. if (OnyxUtils.hasPendingMergeForKey(key)) { @@ -273,7 +273,7 @@ function set(key: TKey, value: NonUndefined): Promise { +function multiSet(data: OnyxMultiSetInput): Promise { const allKeyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(data, true); // When a key is set to null, we need to remove the remove the key from storage using "OnyxUtils.remove". @@ -321,7 +321,7 @@ function multiSet(data: Partial): Promise { * Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1} * Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'} */ -function merge(key: TKey, changes: NonUndefined>>): Promise { +function merge(key: TKey, changes: OnyxMergeInput): Promise { const mergeQueue = OnyxUtils.getMergeQueue(); const mergeQueuePromise = OnyxUtils.getMergeQueuePromise(); @@ -430,7 +430,7 @@ function merge(key: TKey, changes: NonUndefined(collectionKey: TKey, collection: Collection>): Promise { +function mergeCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise { if (typeof collection !== 'object' || Array.isArray(collection) || utils.isEmptyObject(collection)) { Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.'); return Promise.resolve(); @@ -564,7 +564,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise { const keysToBeClearedFromStorage: OnyxKey[] = []; const keyValuesToResetAsCollection: Record> = {}; - const keyValuesToResetIndividually: NullableKeyValueMapping = {}; + const keyValuesToResetIndividually: KeyValueMapping = {}; // The only keys that should not be cleared are: // 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline @@ -619,7 +619,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise { const defaultKeyValuePairs = Object.entries( Object.keys(defaultKeyStates) .filter((key) => !keysToPreserve.includes(key)) - .reduce((obj: NullableKeyValueMapping, key) => { + .reduce((obj: KeyValueMapping, key) => { // eslint-disable-next-line no-param-reassign obj[key] = defaultKeyStates[key]; return obj; diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 7abdeeaa..8403676f 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -19,7 +19,6 @@ import type { DefaultConnectOptions, KeyValueMapping, Mapping, - NullableKeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, @@ -103,7 +102,7 @@ function getDefaultKeyStates(): Record> { * @param initialKeyStates - initial data to set when `init()` and `clear()` are called * @param safeEvictionKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal. */ -function initStoreValues(keys: DeepRecord, initialKeyStates: Partial, safeEvictionKeys: OnyxKey[]): void { +function initStoreValues(keys: DeepRecord, initialKeyStates: Partial, safeEvictionKeys: OnyxKey[]): void { // We need the value of the collection keys later for checking if a // key is a collection. We store it in a map for faster lookup. const collectionValues = Object.values(keys.COLLECTION ?? {}); diff --git a/lib/types.ts b/lib/types.ts index 28f98a2b..5f73f7c2 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -136,7 +136,7 @@ type KeyValueMapping = { * It's very similar to `KeyValueMapping` but this type accepts using `null` as well. */ type NullableKeyValueMapping = { - [TKey in OnyxKey]: OnyxValue; + [TKey in OnyxKey]: NonUndefined> | null; }; /** @@ -180,6 +180,13 @@ type Selector = (value: OnyxEntry */ type OnyxEntry = TOnyxValue | undefined; +/** + * 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 = TOnyxValue | null; + /** * Represents an Onyx collection of entries, that can be either a record of `TOnyxValue`s or `null` / `undefined` if it is empty or doesn't exist. * @@ -261,7 +268,7 @@ type NullishObjectDeep = { * 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 = { +type Collection = { [MapK in keyof TMap]: MapK extends `${TKey}${string}` ? MapK extends `${TKey}` ? never // forbids empty id @@ -321,6 +328,26 @@ type Mapping = ConnectOptions & { connectionID: number; }; +/** + * This represents the value that can be passed to `Onyx.set` and to `Onyx.update` with the method "SET" + */ +type OnyxSetInput = OnyxInput; + +/** + * This represents the value that can be passed to `Onyx.multiSet` and to `Onyx.update` with the method "MULTI_SET" + */ +type OnyxMultiSetInput = Partial; + +/** + * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE" + */ +type OnyxMergeInput = OnyxInput>; + +/** + * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE" + */ +type OnyxMergeCollectionInput = Collection, TMap>; + /** * Represents different kinds of updates that can be passed to `Onyx.update()` method. It is a discriminated union of * different update methods (`SET`, `MERGE`, `MERGE_COLLECTION`), each with their own key and value structure. @@ -331,17 +358,17 @@ type OnyxUpdate = | { onyxMethod: typeof OnyxUtils.METHOD.SET; key: TKey; - value: NonUndefined>; + value: OnyxSetInput; } | { - onyxMethod: typeof OnyxUtils.METHOD.MERGE; + onyxMethod: typeof OnyxUtils.METHOD.MULTI_SET; key: TKey; - value: NonUndefined>>; + value: OnyxMultiSetInput; } | { - onyxMethod: typeof OnyxUtils.METHOD.MULTI_SET; + onyxMethod: typeof OnyxUtils.METHOD.MERGE; key: TKey; - value: Partial; + value: OnyxMergeInput; } | { onyxMethod: typeof OnyxUtils.METHOD.CLEAR; @@ -353,7 +380,7 @@ type OnyxUpdate = [TKey in CollectionKeyBase]: { onyxMethod: typeof OnyxUtils.METHOD.MERGE_COLLECTION; key: TKey; - value: Record<`${TKey}${string}`, NullishDeep>; + value: OnyxMergeCollectionInput; }; }[CollectionKeyBase]; @@ -417,7 +444,12 @@ export type { NullishDeep, OnyxCollection, OnyxEntry, + OnyxInput, OnyxKey, + OnyxSetInput, + OnyxMultiSetInput, + OnyxMergeInput, + OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, Selector, diff --git a/lib/withOnyx/types.ts b/lib/withOnyx/types.ts index 06145551..4b8b85ec 100644 --- a/lib/withOnyx/types.ts +++ b/lib/withOnyx/types.ts @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import type {IsEqual} from 'type-fest'; -import type {CollectionKeyBase, ExtractOnyxCollectionValue, KeyValueMapping, NullableKeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector} from '../types'; +import type {CollectionKeyBase, ExtractOnyxCollectionValue, KeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector} from '../types'; /** * Represents the base mapping options between an Onyx key and the component's prop. @@ -162,7 +162,7 @@ type WithOnyxState = TOnyxProps & { /** * Represents the `withOnyx` internal component instance. */ -type WithOnyxInstance = React.Component> & { +type WithOnyxInstance = React.Component> & { setStateProxy: (modifier: Record> | ((state: Record>) => OnyxValue)) => void; setWithOnyxState: (statePropertyName: OnyxKey, value: OnyxValue) => void; }; From ebbf0e182f7bd896f1d9c604c7202744c111f183 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 31 May 2024 10:45:31 +0200 Subject: [PATCH 2/3] fix: replace never with object --- lib/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types.ts b/lib/types.ts index 5f73f7c2..f1635026 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -346,7 +346,7 @@ type OnyxMergeInput = OnyxInput = Collection, TMap>; +type OnyxMergeCollectionInput = Collection, TMap>; /** * Represents different kinds of updates that can be passed to `Onyx.update()` method. It is a discriminated union of From 72bea5108d637a354c3593ace5113b7c7091bb68 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 31 May 2024 11:15:32 +0200 Subject: [PATCH 3/3] export types --- lib/index.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/index.ts b/lib/index.ts index 79bcf856..3352d39e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,6 +1,20 @@ import type {ConnectOptions, OnyxUpdate} from './Onyx'; import Onyx from './Onyx'; -import type {CustomTypeOptions, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector} from './types'; +import type { + CustomTypeOptions, + KeyValueMapping, + NullishDeep, + OnyxCollection, + OnyxEntry, + OnyxInput, + OnyxKey, + OnyxValue, + Selector, + OnyxSetInput, + OnyxMultiSetInput, + OnyxMergeInput, + OnyxMergeCollectionInput, +} from './types'; import type {FetchStatus, ResultMetadata, UseOnyxResult} from './useOnyx'; import useOnyx from './useOnyx'; import withOnyx from './withOnyx'; @@ -16,7 +30,12 @@ export type { NullishDeep, OnyxCollection, OnyxEntry, + OnyxInput, OnyxKey, + OnyxSetInput, + OnyxMultiSetInput, + OnyxMergeInput, + OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata,