react-native-onyx 2.0.45 → 2.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Onyx.js CHANGED
@@ -197,9 +197,13 @@ function set(key, value) {
197
197
  if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
198
198
  delete OnyxUtils_1.default.getMergeQueue()[key];
199
199
  }
200
+ // Onyx.set will ignore `undefined` values as inputs, therefore we can return early.
201
+ if (value === undefined) {
202
+ return Promise.resolve();
203
+ }
200
204
  const existingValue = OnyxCache_1.default.get(key, false);
201
205
  // If the existing value as well as the new value are null, we can return early.
202
- if (value === null && existingValue === null) {
206
+ if (existingValue === undefined && value === null) {
203
207
  return Promise.resolve();
204
208
  }
205
209
  // Check if the value is compatible with the existing value in the storage
@@ -244,28 +248,18 @@ function set(key, value) {
244
248
  * @param data object keyed by ONYXKEYS and the values to set
245
249
  */
246
250
  function multiSet(data) {
247
- const allKeyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(data, true);
248
- // When a key is set to null, we need to remove the remove the key from storage using "OnyxUtils.remove".
249
- // Therefore, we filter the key value pairs to exclude null values and remove those keys explicitly.
250
- const removePromises = [];
251
- const keyValuePairsToUpdate = allKeyValuePairs.filter(([key, value]) => {
252
- if (value === null) {
253
- removePromises.push(OnyxUtils_1.default.remove(key));
254
- return false;
255
- }
256
- return true;
257
- });
258
- const updatePromises = keyValuePairsToUpdate.map(([key, value]) => {
251
+ const keyValuePairsToSet = OnyxUtils_1.default.prepareKeyValuePairsForStorage(data, true);
252
+ const updatePromises = keyValuePairsToSet.map(([key, value]) => {
259
253
  const prevValue = OnyxCache_1.default.get(key, false);
260
254
  // Update cache and optimistically inform subscribers on the next tick
261
255
  OnyxCache_1.default.set(key, value);
262
256
  return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, prevValue);
263
257
  });
264
- return storage_1.default.multiSet(allKeyValuePairs)
258
+ return storage_1.default.multiSet(keyValuePairsToSet)
265
259
  .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSet, data))
266
260
  .then(() => {
267
261
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MULTI_SET, undefined, data);
268
- return Promise.all([removePromises, updatePromises]);
262
+ return Promise.all(updatePromises);
269
263
  })
270
264
  .then(() => undefined);
271
265
  }
@@ -507,7 +501,7 @@ function clear(keysToPreserve = []) {
507
501
  // since collection key subscribers need to be updated differently
508
502
  if (!isKeyToPreserve) {
509
503
  const oldValue = OnyxCache_1.default.get(key);
510
- const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : null;
504
+ const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : undefined;
511
505
  if (newValue !== oldValue) {
512
506
  OnyxCache_1.default.set(key, newValue);
513
507
  const collectionKey = key.substring(0, key.indexOf('_') + 1);
@@ -40,6 +40,8 @@ declare class OnyxCache {
40
40
  addKey(key: OnyxKey): void;
41
41
  /** Used to set keys that are null/undefined in storage without adding null to the storage map */
42
42
  addNullishStorageKey(key: OnyxKey): void;
43
+ /** Used to set keys that are null/undefined in storage without adding null to the storage map */
44
+ hasNullishStorageKey(key: OnyxKey): boolean;
43
45
  /** Used to clear keys that are null/undefined in cache */
44
46
  clearNullishStorageKeys(): void;
45
47
  /** Check whether cache has data for the given key */
package/dist/OnyxCache.js CHANGED
@@ -20,7 +20,7 @@ class OnyxCache {
20
20
  this.storageMap = {};
21
21
  this.pendingPromises = new Map();
22
22
  // bind all public methods to prevent problems with `this`
23
- (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys');
23
+ (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys');
24
24
  }
25
25
  /** Get all the storage keys */
26
26
  getAllKeys() {
@@ -50,13 +50,17 @@ class OnyxCache {
50
50
  addNullishStorageKey(key) {
51
51
  this.nullishStorageKeys.add(key);
52
52
  }
53
+ /** Used to set keys that are null/undefined in storage without adding null to the storage map */
54
+ hasNullishStorageKey(key) {
55
+ return this.nullishStorageKeys.has(key);
56
+ }
53
57
  /** Used to clear keys that are null/undefined in cache */
54
58
  clearNullishStorageKeys() {
55
59
  this.nullishStorageKeys = new Set();
56
60
  }
57
61
  /** Check whether cache has data for the given key */
58
62
  hasCacheForKey(key) {
59
- return this.storageMap[key] !== undefined || this.nullishStorageKeys.has(key);
63
+ return this.storageMap[key] !== undefined || this.hasNullishStorageKey(key);
60
64
  }
61
65
  /**
62
66
  * Get a cached value from storage
@@ -1,6 +1,6 @@
1
1
  import type { ValueOf } from 'type-fest';
2
2
  import type Onyx from './Onyx';
3
- import type { CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, WithOnyxConnectOptions } from './types';
3
+ import type { CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxValue, WithOnyxConnectOptions } from './types';
4
4
  declare const METHOD: {
5
5
  readonly SET: "set";
6
6
  readonly MERGE: "merge";
@@ -52,7 +52,7 @@ declare function sendActionToDevTools(method: Exclude<OnyxMethod, typeof METHOD.
52
52
  declare function maybeFlushBatchUpdates(): Promise<void>;
53
53
  declare function batchUpdates(updates: () => void): Promise<void>;
54
54
  /** Get some data from the store */
55
- declare function get(key: OnyxKey): Promise<OnyxValue<OnyxKey>>;
55
+ declare function get<TKey extends OnyxKey, TValue extends OnyxValue<TKey>>(key: TKey): Promise<TValue>;
56
56
  /** Returns current key names stored in persisted storage */
57
57
  declare function getAllKeys(): Promise<Set<OnyxKey>>;
58
58
  /**
@@ -159,8 +159,8 @@ declare function evictStorageAndRetry<TMethod extends typeof Onyx.set | typeof O
159
159
  */
160
160
  declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean): Promise<void>;
161
161
  declare function hasPendingMergeForKey(key: OnyxKey): boolean;
162
- type RemoveNullValuesOutput<Value extends OnyxValue<OnyxKey> | null> = {
163
- value: Value | null;
162
+ type RemoveNullValuesOutput<Value extends OnyxInput<OnyxKey> | undefined> = {
163
+ value: Value;
164
164
  wasRemoved: boolean;
165
165
  };
166
166
  /**
@@ -170,7 +170,7 @@ type RemoveNullValuesOutput<Value extends OnyxValue<OnyxKey> | null> = {
170
170
  *
171
171
  * @returns The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
172
172
  */
173
- declare function removeNullValues<Value extends OnyxValue<OnyxKey>>(key: OnyxKey, value: Value | null, shouldRemoveNestedNulls?: boolean): RemoveNullValuesOutput<Value>;
173
+ declare function removeNullValues<Value extends OnyxInput<OnyxKey> | undefined>(key: OnyxKey, value: Value, shouldRemoveNestedNulls?: boolean): RemoveNullValuesOutput<Value>;
174
174
  /**
175
175
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
176
176
  * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
@@ -178,13 +178,13 @@ declare function removeNullValues<Value extends OnyxValue<OnyxKey>>(key: OnyxKey
178
178
 
179
179
  * @return an array of key - value pairs <[key, value]>
180
180
  */
181
- declare function prepareKeyValuePairsForStorage(data: Record<OnyxKey, OnyxValue<OnyxKey>>, shouldRemoveNestedNulls: boolean): Array<[OnyxKey, OnyxValue<OnyxKey>]>;
181
+ declare function prepareKeyValuePairsForStorage(data: Record<OnyxKey, OnyxInput<OnyxKey>>, shouldRemoveNestedNulls: boolean): Array<[OnyxKey, OnyxInput<OnyxKey>]>;
182
182
  /**
183
183
  * Merges an array of changes with an existing value
184
184
  *
185
185
  * @param changes Array of changes that should be applied to the existing value
186
186
  */
187
- declare function applyMerge(existingValue: OnyxValue<OnyxKey>, changes: Array<OnyxValue<OnyxKey>>, shouldRemoveNestedNulls: boolean): OnyxValue<OnyxKey>;
187
+ declare function applyMerge<TValue extends OnyxInput<OnyxKey> | undefined, TChange extends OnyxInput<OnyxKey> | undefined>(existingValue: TValue, changes: TChange[], shouldRemoveNestedNulls: boolean): TChange;
188
188
  /**
189
189
  * Merge user provided default key value pairs.
190
190
  */
package/dist/OnyxUtils.js CHANGED
@@ -338,6 +338,10 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
338
338
  if (!collectionMemberKeys && !isCollectionMemberKey(collectionKey, key)) {
339
339
  return;
340
340
  }
341
+ const cachedValue = OnyxCache_1.default.get(key);
342
+ if (cachedValue === undefined && !OnyxCache_1.default.hasNullishStorageKey(key)) {
343
+ return;
344
+ }
341
345
  collection[key] = OnyxCache_1.default.get(key);
342
346
  });
343
347
  return collection;
@@ -349,10 +353,6 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
349
353
  // We prepare the "cached collection" which is the entire collection + the new partial data that
350
354
  // was merged in via mergeCollection().
351
355
  const cachedCollection = getCachedCollection(collectionKey);
352
- // If the previous collection equals the new collection then we do not need to notify any subscribers.
353
- if (partialPreviousCollection !== undefined && (0, fast_equals_1.deepEqual)(cachedCollection, partialPreviousCollection)) {
354
- return;
355
- }
356
356
  const previousCollection = partialPreviousCollection !== null && partialPreviousCollection !== void 0 ? partialPreviousCollection : {};
357
357
  // We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or
358
358
  // individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection
@@ -426,23 +426,27 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
426
426
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
427
427
  const previousData = prevState[subscriber.statePropertyName];
428
428
  const newData = reduceCollectionWithSelector(cachedCollection, collectionSelector, subscriber.withOnyxInstance.state);
429
- if (!(0, fast_equals_1.deepEqual)(previousData, newData)) {
430
- return {
431
- [subscriber.statePropertyName]: newData,
432
- };
429
+ if ((0, fast_equals_1.deepEqual)(previousData, newData)) {
430
+ return null;
433
431
  }
434
- return null;
432
+ return {
433
+ [subscriber.statePropertyName]: newData,
434
+ };
435
435
  });
436
436
  continue;
437
437
  }
438
438
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
439
439
  var _a;
440
- const finalCollection = (0, clone_1.default)((_a = prevState === null || prevState === void 0 ? void 0 : prevState[subscriber.statePropertyName]) !== null && _a !== void 0 ? _a : {});
440
+ const prevCollection = (_a = prevState === null || prevState === void 0 ? void 0 : prevState[subscriber.statePropertyName]) !== null && _a !== void 0 ? _a : {};
441
+ const finalCollection = (0, clone_1.default)(prevCollection);
441
442
  const dataKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
442
443
  for (let j = 0; j < dataKeys.length; j++) {
443
444
  const dataKey = dataKeys[j];
444
445
  finalCollection[dataKey] = cachedCollection[dataKey];
445
446
  }
447
+ if ((0, fast_equals_1.deepEqual)(prevCollection, finalCollection)) {
448
+ return null;
449
+ }
446
450
  PerformanceUtils.logSetStateCall(subscriber, prevState === null || prevState === void 0 ? void 0 : prevState[subscriber.statePropertyName], finalCollection, 'keysChanged', collectionKey);
447
451
  return {
448
452
  [subscriber.statePropertyName]: finalCollection,
@@ -469,29 +473,29 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
469
473
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
470
474
  const prevData = prevState[subscriber.statePropertyName];
471
475
  const newData = selector(cachedCollection[subscriber.key], subscriber.withOnyxInstance.state);
472
- if (!(0, fast_equals_1.deepEqual)(prevData, newData)) {
473
- PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
474
- return {
475
- [subscriber.statePropertyName]: newData,
476
- };
476
+ if ((0, fast_equals_1.deepEqual)(prevData, newData)) {
477
+ return null;
477
478
  }
478
- return null;
479
+ PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
480
+ return {
481
+ [subscriber.statePropertyName]: newData,
482
+ };
479
483
  });
480
484
  continue;
481
485
  }
482
486
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
483
- const data = cachedCollection[subscriber.key];
484
- const previousData = prevState[subscriber.statePropertyName];
487
+ const prevData = prevState[subscriber.statePropertyName];
488
+ const newData = cachedCollection[subscriber.key];
485
489
  // Avoids triggering unnecessary re-renders when feeding empty objects
486
- if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(previousData)) {
490
+ if (utils_1.default.isEmptyObject(newData) && utils_1.default.isEmptyObject(prevData)) {
487
491
  return null;
488
492
  }
489
- if (data === previousData) {
493
+ if ((0, fast_equals_1.deepEqual)(prevData, newData)) {
490
494
  return null;
491
495
  }
492
- PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keysChanged', collectionKey);
496
+ PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
493
497
  return {
494
- [subscriber.statePropertyName]: data,
498
+ [subscriber.statePropertyName]: newData,
495
499
  };
496
500
  });
497
501
  }
@@ -553,20 +557,23 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
553
557
  [key]: selector(value, subscriber.withOnyxInstance.state),
554
558
  };
555
559
  const prevDataWithNewData = Object.assign(Object.assign({}, prevWithOnyxData), newWithOnyxData);
556
- if (!(0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
557
- PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
558
- return {
559
- [subscriber.statePropertyName]: prevDataWithNewData,
560
- };
560
+ if ((0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
561
+ return null;
561
562
  }
562
- return null;
563
+ PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
564
+ return {
565
+ [subscriber.statePropertyName]: prevDataWithNewData,
566
+ };
563
567
  });
564
568
  continue;
565
569
  }
566
570
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
567
- const collection = prevState[subscriber.statePropertyName] || {};
568
- const newCollection = Object.assign(Object.assign({}, collection), { [key]: value });
569
- PerformanceUtils.logSetStateCall(subscriber, collection, newCollection, 'keyChanged', key);
571
+ const prevCollection = prevState[subscriber.statePropertyName] || {};
572
+ const newCollection = Object.assign(Object.assign({}, prevCollection), { [key]: value });
573
+ if ((0, fast_equals_1.deepEqual)(prevCollection, newCollection)) {
574
+ return null;
575
+ }
576
+ PerformanceUtils.logSetStateCall(subscriber, prevCollection, newCollection, 'keyChanged', key);
570
577
  return {
571
578
  [subscriber.statePropertyName]: newCollection,
572
579
  };
@@ -579,12 +586,12 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
579
586
  subscriber.withOnyxInstance.setStateProxy(() => {
580
587
  const prevValue = selector(previousValue, subscriber.withOnyxInstance.state);
581
588
  const newValue = selector(value, subscriber.withOnyxInstance.state);
582
- if (!(0, fast_equals_1.deepEqual)(prevValue, newValue)) {
583
- return {
584
- [subscriber.statePropertyName]: newValue,
585
- };
589
+ if ((0, fast_equals_1.deepEqual)(prevValue, newValue)) {
590
+ return null;
586
591
  }
587
- return null;
592
+ return {
593
+ [subscriber.statePropertyName]: newValue,
594
+ };
588
595
  });
589
596
  continue;
590
597
  }
@@ -763,7 +770,7 @@ function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
763
770
  function remove(key) {
764
771
  const prevValue = OnyxCache_1.default.get(key, false);
765
772
  OnyxCache_1.default.drop(key);
766
- scheduleSubscriberUpdate(key, null, prevValue);
773
+ scheduleSubscriberUpdate(key, undefined, prevValue);
767
774
  return storage_1.default.removeItem(key).then(() => undefined);
768
775
  }
769
776
  function reportStorageQuota() {
@@ -829,7 +836,10 @@ function hasPendingMergeForKey(key) {
829
836
  function removeNullValues(key, value, shouldRemoveNestedNulls = true) {
830
837
  if (value === null) {
831
838
  remove(key);
832
- return { value: null, wasRemoved: true };
839
+ return { value, wasRemoved: true };
840
+ }
841
+ if (value === undefined) {
842
+ return { value, wasRemoved: false };
833
843
  }
834
844
  // We can remove all null values in an object by merging it with itself
835
845
  // utils.fastMerge recursively goes through the object and removes all null values
@@ -846,7 +856,7 @@ function removeNullValues(key, value, shouldRemoveNestedNulls = true) {
846
856
  function prepareKeyValuePairsForStorage(data, shouldRemoveNestedNulls) {
847
857
  return Object.entries(data).reduce((pairs, [key, value]) => {
848
858
  const { value: valueAfterRemoving, wasRemoved } = removeNullValues(key, value, shouldRemoveNestedNulls);
849
- if (!wasRemoved) {
859
+ if (!wasRemoved && valueAfterRemoving !== undefined) {
850
860
  pairs.push([key, valueAfterRemoving]);
851
861
  }
852
862
  return pairs;
@@ -864,7 +874,7 @@ function applyMerge(existingValue, changes, shouldRemoveNestedNulls) {
864
874
  }
865
875
  if (changes.some((change) => change && typeof change === 'object')) {
866
876
  // Object values are then merged one after the other
867
- return changes.reduce((modifiedData, change) => utils_1.default.fastMerge(modifiedData, change, shouldRemoveNestedNulls), existingValue || {});
877
+ return changes.reduce((modifiedData, change) => utils_1.default.fastMerge(modifiedData, change, shouldRemoveNestedNulls), (existingValue || {}));
868
878
  }
869
879
  // If we have anything else we can't merge it so we'll
870
880
  // simply return the last value that was queued
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { ConnectOptions, OnyxUpdate } from './Onyx';
2
2
  import Onyx from './Onyx';
3
- import type { CustomTypeOptions, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxValue, Selector, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput } from './types';
3
+ import type { CustomTypeOptions, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput } from './types';
4
4
  import type { FetchStatus, ResultMetadata, UseOnyxResult } from './useOnyx';
5
5
  import useOnyx from './useOnyx';
6
6
  import withOnyx from './withOnyx';
7
7
  import type { WithOnyxState } from './withOnyx/types';
8
8
  export default Onyx;
9
9
  export { useOnyx, withOnyx };
10
- export type { ConnectOptions, CustomTypeOptions, FetchStatus, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata, Selector, UseOnyxResult, WithOnyxState, };
10
+ export type { ConnectOptions, CustomTypeOptions, FetchStatus, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata, Selector, UseOnyxResult, WithOnyxState, };
package/dist/types.d.ts CHANGED
@@ -99,30 +99,6 @@ type CollectionKey = `${CollectionKeyBase}${string}`;
99
99
  * Represents a string union of all Onyx normal and collection keys.
100
100
  */
101
101
  type OnyxKey = Key | CollectionKey;
102
- /**
103
- * Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided.
104
- */
105
- type OnyxValue<TKey extends OnyxKey> = string extends TKey ? unknown : TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>;
106
- /**
107
- * Represents a mapping of Onyx keys to values, where keys are either normal or collection Onyx keys
108
- * and values are the corresponding values in Onyx's state.
109
- *
110
- * For collection keys, `KeyValueMapping` allows any string to be appended
111
- * to the key (e.g., 'report_some-id', 'download_some-id').
112
- *
113
- * The mapping is derived from the `values` property of the `TypeOptions` type.
114
- */
115
- type KeyValueMapping = {
116
- [TKey in keyof TypeOptions['values'] as TKey extends CollectionKeyBase ? `${TKey}${string}` : TKey]: TypeOptions['values'][TKey];
117
- };
118
- /**
119
- * Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`.
120
- *
121
- * It's very similar to `KeyValueMapping` but this type accepts using `null` as well.
122
- */
123
- type NullableKeyValueMapping = {
124
- [TKey in OnyxKey]: NonUndefined<OnyxValue<TKey>> | null;
125
- };
126
102
  /**
127
103
  * Represents a selector function type which operates based on the provided `TKey` and `ReturnType`.
128
104
  *
@@ -134,7 +110,7 @@ type NullableKeyValueMapping = {
134
110
  */
135
111
  type Selector<TKey extends OnyxKey, TOnyxProps, TReturnType> = (value: OnyxEntry<KeyValueMapping[TKey]>, state?: WithOnyxState<TOnyxProps>) => TReturnType;
136
112
  /**
137
- * Represents a single Onyx entry, that can be either `TOnyxValue` or `null` / `undefined` if it doesn't exist.
113
+ * Represents a single Onyx entry, that can be either `TOnyxValue` or `undefined` if it doesn't exist.
138
114
  *
139
115
  * It can be used to specify data retrieved from Onyx e.g. `withOnyx` HOC mappings.
140
116
  *
@@ -163,13 +139,7 @@ type Selector<TKey extends OnyxKey, TOnyxProps, TReturnType> = (value: OnyxEntry
163
139
  */
164
140
  type OnyxEntry<TOnyxValue> = TOnyxValue | undefined;
165
141
  /**
166
- * Represents an input value that can be passed to Onyx methods, that can be either `TOnyxValue` or `null`.
167
- * Setting a key to `null` will remove the key from the store.
168
- * `undefined` is not allowed for setting values, because it will have no effect on the data.
169
- */
170
- type OnyxInput<TOnyxValue> = TOnyxValue | null;
171
- /**
172
- * 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.
142
+ * 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.
173
143
  *
174
144
  * It can be used to specify collection data retrieved from Onyx e.g. `withOnyx` HOC mappings.
175
145
  *
@@ -197,6 +167,22 @@ type OnyxInput<TOnyxValue> = TOnyxValue | null;
197
167
  * ```
198
168
  */
199
169
  type OnyxCollection<TOnyxValue> = OnyxEntry<Record<string, TOnyxValue | undefined>>;
170
+ /**
171
+ * Represents a mapping of Onyx keys to values, where keys are either normal or collection Onyx keys
172
+ * and values are the corresponding values in Onyx's state.
173
+ *
174
+ * For collection keys, `KeyValueMapping` allows any string to be appended
175
+ * to the key (e.g., 'report_some-id', 'download_some-id').
176
+ *
177
+ * The mapping is derived from the `values` property of the `TypeOptions` type.
178
+ */
179
+ type KeyValueMapping = {
180
+ [TKey in keyof TypeOptions['values'] as TKey extends CollectionKeyBase ? `${TKey}${string}` : TKey]: TypeOptions['values'][TKey];
181
+ };
182
+ /**
183
+ * Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided.
184
+ */
185
+ type OnyxValue<TKey extends OnyxKey> = string extends TKey ? unknown : TKey extends CollectionKeyBase ? OnyxCollection<KeyValueMapping[TKey]> : OnyxEntry<KeyValueMapping[TKey]>;
200
186
  /** Utility type to extract `TOnyxValue` from `OnyxCollection<TOnyxValue>` */
201
187
  type ExtractOnyxCollectionValue<TOnyxCollection> = TOnyxCollection extends NonNullable<OnyxCollection<infer U>> ? U : never;
202
188
  type NonTransformableTypes = BuiltIns | ((...args: any[]) => unknown) | Map<unknown, unknown> | Set<unknown> | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | unknown[] | readonly unknown[];
@@ -251,7 +237,7 @@ type WithOnyxConnectOptions<TKey extends OnyxKey> = {
251
237
  selector?: Selector<TKey, unknown, unknown>;
252
238
  canEvict?: boolean;
253
239
  };
254
- type DefaultConnectCallback<TKey extends OnyxKey> = (value: NonUndefined<OnyxEntry<KeyValueMapping[TKey]>>, key: TKey) => void;
240
+ type DefaultConnectCallback<TKey extends OnyxKey> = (value: OnyxEntry<KeyValueMapping[TKey]>, key: TKey) => void;
255
241
  type CollectionConnectCallback<TKey extends OnyxKey> = (value: NonUndefined<OnyxCollection<KeyValueMapping[TKey]>>) => void;
256
242
  /** Represents the callback function used in `Onyx.connect()` method with a regular key. */
257
243
  type DefaultConnectOptions<TKey extends OnyxKey> = {
@@ -282,22 +268,46 @@ type ConnectOptions<TKey extends OnyxKey> = (CollectionConnectOptions<TKey> | De
282
268
  type Mapping<TKey extends OnyxKey> = ConnectOptions<TKey> & {
283
269
  connectionID: number;
284
270
  };
271
+ /**
272
+ * Represents a single Onyx input value, that can be either `TOnyxValue` or `null` if the key should be deleted.
273
+ * This type is used for data passed to Onyx e.g. in `Onyx.merge` and `Onyx.set`.
274
+ */
275
+ type OnyxInputValue<TOnyxValue> = TOnyxValue | null;
276
+ /**
277
+ * Represents an Onyx collection input, that can be either a record of `TOnyxValue`s or `null` if the key should be deleted.
278
+ */
279
+ type OnyxCollectionInputValue<TOnyxValue> = OnyxInputValue<Record<string, TOnyxValue | null>>;
280
+ /**
281
+ * Represents an input value that can be passed to Onyx methods, that can be either `TOnyxValue` or `null`.
282
+ * Setting a key to `null` will remove the key from the store.
283
+ * `undefined` is not allowed for setting values, because it will have no effect on the data.
284
+ */
285
+ type OnyxInput<TKey extends OnyxKey> = OnyxInputValue<NullishDeep<KeyValueMapping[TKey]>>;
286
+ /**
287
+ * Represents a mapping object where each `OnyxKey` maps to either a value of its corresponding type in `KeyValueMapping` or `null`.
288
+ *
289
+ * It's very similar to `KeyValueMapping` but this type is used for inputs to Onyx
290
+ * (set, merge, mergeCollection) and therefore accepts using `null` to remove a key from Onyx.
291
+ */
292
+ type OnyxInputKeyValueMapping = {
293
+ [TKey in OnyxKey]: OnyxInput<TKey>;
294
+ };
285
295
  /**
286
296
  * This represents the value that can be passed to `Onyx.set` and to `Onyx.update` with the method "SET"
287
297
  */
288
- type OnyxSetInput<TKey extends OnyxKey> = OnyxInput<KeyValueMapping[TKey]>;
298
+ type OnyxSetInput<TKey extends OnyxKey> = OnyxInput<TKey>;
289
299
  /**
290
300
  * This represents the value that can be passed to `Onyx.multiSet` and to `Onyx.update` with the method "MULTI_SET"
291
301
  */
292
- type OnyxMultiSetInput = Partial<NullableKeyValueMapping>;
302
+ type OnyxMultiSetInput = Partial<OnyxInputKeyValueMapping>;
293
303
  /**
294
304
  * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE"
295
305
  */
296
- type OnyxMergeInput<TKey extends OnyxKey> = OnyxInput<NullishDeep<KeyValueMapping[TKey]>>;
306
+ type OnyxMergeInput<TKey extends OnyxKey> = OnyxInput<TKey>;
297
307
  /**
298
308
  * This represents the value that can be passed to `Onyx.merge` and to `Onyx.update` with the method "MERGE"
299
309
  */
300
- type OnyxMergeCollectionInput<TKey extends OnyxKey, TMap = object> = Collection<TKey, NullishDeep<KeyValueMapping[TKey]>, TMap>;
310
+ type OnyxMergeCollectionInput<TKey extends OnyxKey, TMap = object> = Collection<TKey, NonNullable<OnyxInput<TKey>>, TMap>;
301
311
  /**
302
312
  * Represents different kinds of updates that can be passed to `Onyx.update()` method. It is a discriminated union of
303
313
  * different update methods (`SET`, `MERGE`, `MERGE_COLLECTION`), each with their own key and value structure.
@@ -334,7 +344,7 @@ type InitOptions = {
334
344
  /** `ONYXKEYS` constants object */
335
345
  keys?: DeepRecord<string, OnyxKey>;
336
346
  /** initial data to set when `init()` and `clear()` is called */
337
- initialKeyStates?: Partial<NullableKeyValueMapping>;
347
+ initialKeyStates?: Partial<OnyxInputKeyValueMapping>;
338
348
  /**
339
349
  * This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged
340
350
  * as "safe" for removal. Any components subscribing to these keys must also implement a canEvict option. See the README for more info.
@@ -355,4 +365,4 @@ type InitOptions = {
355
365
  debugSetState?: boolean;
356
366
  };
357
367
  type GenericFunction = (...args: any[]) => any;
358
- export type { BaseConnectOptions, Collection, CollectionConnectCallback, CollectionConnectOptions, CollectionKey, CollectionKeyBase, ConnectOptions, CustomTypeOptions, DeepRecord, DefaultConnectCallback, DefaultConnectOptions, ExtractOnyxCollectionValue, GenericFunction, InitOptions, Key, KeyValueMapping, Mapping, NonNull, NonUndefined, NullableKeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, Selector, WithOnyxConnectOptions, };
368
+ 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, OnyxUpdate, OnyxValue, Selector, WithOnyxConnectOptions, };
package/dist/useOnyx.d.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  import type { IsEqual } from 'type-fest';
2
- import type { CollectionKeyBase, KeyValueMapping, NonNull, OnyxCollection, OnyxEntry, OnyxKey, Selector } from './types';
3
- /**
4
- * Represents a Onyx value that can be either a single entry or a collection of entries, depending on the `TKey` provided.
5
- * It's a variation of `OnyxValue` type that is read-only and excludes the `null` type.
6
- */
7
- type UseOnyxValue<TKey extends OnyxKey> = string extends TKey ? unknown : TKey extends CollectionKeyBase ? NonNull<OnyxCollection<KeyValueMapping[TKey]>> : NonNull<OnyxEntry<KeyValueMapping[TKey]>>;
2
+ import type { CollectionKeyBase, OnyxCollection, OnyxKey, OnyxValue, Selector } from './types';
8
3
  type BaseUseOnyxOptions = {
9
4
  /**
10
5
  * Determines if this key in this subscription is safe to be evicted.
@@ -35,12 +30,12 @@ type UseOnyxSelectorOption<TKey extends OnyxKey, TReturnValue> = {
35
30
  selector?: Selector<TKey, unknown, TReturnValue>;
36
31
  };
37
32
  type FetchStatus = 'loading' | 'loaded';
38
- type CachedValue<TKey extends OnyxKey, TValue> = IsEqual<TValue, UseOnyxValue<TKey>> extends true ? TValue : TKey extends CollectionKeyBase ? NonNullable<OnyxCollection<TValue>> : TValue;
33
+ type CachedValue<TKey extends OnyxKey, TValue> = IsEqual<TValue, OnyxValue<TKey>> extends true ? TValue : TKey extends CollectionKeyBase ? NonNullable<OnyxCollection<TValue>> : TValue;
39
34
  type ResultMetadata = {
40
35
  status: FetchStatus;
41
36
  };
42
37
  type UseOnyxResult<TKey extends OnyxKey, TValue> = [CachedValue<TKey, TValue>, ResultMetadata];
43
- declare function useOnyx<TKey extends OnyxKey, TReturnValue = UseOnyxValue<TKey>>(key: TKey, options?: BaseUseOnyxOptions & UseOnyxInitialValueOption<TReturnValue> & Required<UseOnyxSelectorOption<TKey, TReturnValue>>): UseOnyxResult<TKey, TReturnValue>;
44
- declare function useOnyx<TKey extends OnyxKey, TReturnValue = UseOnyxValue<TKey>>(key: TKey, options?: BaseUseOnyxOptions & UseOnyxInitialValueOption<NoInfer<TReturnValue>>): UseOnyxResult<TKey, TReturnValue>;
38
+ declare function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey, options?: BaseUseOnyxOptions & UseOnyxInitialValueOption<TReturnValue> & Required<UseOnyxSelectorOption<TKey, TReturnValue>>): UseOnyxResult<TKey, TReturnValue>;
39
+ declare function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey, options?: BaseUseOnyxOptions & UseOnyxInitialValueOption<NoInfer<TReturnValue>>): UseOnyxResult<TKey, TReturnValue>;
45
40
  export default useOnyx;
46
41
  export type { UseOnyxResult, ResultMetadata, FetchStatus };
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OnyxKey, OnyxValue } from './types';
1
+ import type { OnyxInput, OnyxKey } from './types';
2
2
  type EmptyObject = Record<string, never>;
3
3
  type EmptyValue = EmptyObject | null | undefined;
4
4
  /** Checks whether the given object is an object and not null/undefined. */
@@ -10,9 +10,9 @@ declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
10
10
  * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
11
11
  * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
12
12
  */
13
- declare function fastMerge<TObject extends Record<string, unknown>>(target: TObject | null, source: TObject | null, shouldRemoveNestedNulls?: boolean): TObject | null;
13
+ declare function fastMerge<TValue>(target: TValue, source: TValue, shouldRemoveNestedNulls?: boolean): TValue;
14
14
  /** Deep removes the nested null values from the given value. */
15
- declare function removeNestedNullValues<TValue extends OnyxValue<OnyxKey> | unknown | unknown[]>(value: TValue | null): TValue | null;
15
+ declare function removeNestedNullValues<TValue extends OnyxInput<OnyxKey> | null>(value: TValue): TValue;
16
16
  /** Formats the action name by uppercasing and adding the key if provided. */
17
17
  declare function formatActionName(method: string, key?: OnyxKey): string;
18
18
  /** validate that the update and the existing value are compatible */
package/dist/utils.js CHANGED
@@ -22,19 +22,23 @@ function isMergeableObject(value) {
22
22
  */
23
23
  function mergeObject(target, source, shouldRemoveNestedNulls = true) {
24
24
  const destination = {};
25
+ const targetObject = isMergeableObject(target) ? target : undefined;
25
26
  // First we want to copy over all keys from the target into the destination object,
26
27
  // in case "target" is a mergable object.
27
28
  // If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object
28
29
  // and therefore we need to omit keys where either the source or target value is null.
29
- if (isMergeableObject(target)) {
30
- const targetKeys = Object.keys(target);
30
+ if (targetObject) {
31
+ const targetKeys = Object.keys(targetObject);
31
32
  for (let i = 0; i < targetKeys.length; ++i) {
32
33
  const key = targetKeys[i];
33
34
  const sourceValue = source === null || source === void 0 ? void 0 : source[key];
34
- const targetValue = target === null || target === void 0 ? void 0 : target[key];
35
+ const targetValue = targetObject === null || targetObject === void 0 ? void 0 : targetObject[key];
35
36
  // If "shouldRemoveNestedNulls" is true, we want to remove null values from the merged object.
36
37
  // Therefore, if either target or source value is null, we want to prevent the key from being set.
37
- const isSourceOrTargetNull = targetValue === null || sourceValue === null;
38
+ // targetValue should techincally never be "undefined", because it will always be a value from cache or storage
39
+ // and we never set "undefined" there. Still, if there targetValue is undefined we don't want to set
40
+ // the key explicitly to prevent loose undefined values in objects in cache and storage.
41
+ const isSourceOrTargetNull = targetValue === undefined || targetValue === null || sourceValue === null;
38
42
  const shouldOmitTargetKey = shouldRemoveNestedNulls && isSourceOrTargetNull;
39
43
  if (!shouldOmitTargetKey) {
40
44
  destination[key] = targetValue;
@@ -46,7 +50,7 @@ function mergeObject(target, source, shouldRemoveNestedNulls = true) {
46
50
  for (let i = 0; i < sourceKeys.length; ++i) {
47
51
  const key = sourceKeys[i];
48
52
  const sourceValue = source === null || source === void 0 ? void 0 : source[key];
49
- const targetValue = target === null || target === void 0 ? void 0 : target[key];
53
+ const targetValue = targetObject === null || targetObject === void 0 ? void 0 : targetObject[key];
50
54
  // If undefined is passed as the source value for a key, we want to generally ignore it.
51
55
  // If "shouldRemoveNestedNulls" is set to true and the source value is null,
52
56
  // we don't want to set/merge the source value into the merged object.
@@ -207,7 +207,7 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
207
207
  }
208
208
  this.tempState[statePropertyName] = val;
209
209
  // If some key does not have a value yet, do not update the state yet
210
- const tempStateIsMissingKey = requiredKeysForInit.some((key) => { var _a; return ((_a = this.tempState) === null || _a === void 0 ? void 0 : _a[key]) === undefined; });
210
+ const tempStateIsMissingKey = requiredKeysForInit.some((key) => { var _a; return !(key in ((_a = this.tempState) !== null && _a !== void 0 ? _a : {})); });
211
211
  if (tempStateIsMissingKey) {
212
212
  return;
213
213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.45",
3
+ "version": "2.0.47",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -111,8 +111,8 @@
111
111
  }
112
112
  },
113
113
  "engines": {
114
- "node": ">=20.10.0",
115
- "npm": ">=10.2.3"
114
+ "node": ">=20.14.0",
115
+ "npm": ">=10.7.0"
116
116
  },
117
117
  "sideEffects": false
118
118
  }