react-native-onyx 2.0.137 → 2.0.139

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
@@ -155,6 +155,13 @@ function disconnect(connection) {
155
155
  * @param options optional configuration object
156
156
  */
157
157
  function set(key, value, options) {
158
+ return setInternal(key, value, options);
159
+ }
160
+ /**
161
+ * @param isFromUpdate - Whether this call originates from Onyx.update()
162
+ * When isFromUpdate = true (called from Onyx.update()), useOnyx hook subscribers are batched to escape excessive re-renders in components
163
+ */
164
+ function setInternal(key, value, options, isFromUpdate = false) {
158
165
  // When we use Onyx.set to set a key we want to clear the current delta changes from Onyx.merge that were queued
159
166
  // before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
160
167
  if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
@@ -200,13 +207,13 @@ function set(key, value, options) {
200
207
  const hasChanged = (options === null || options === void 0 ? void 0 : options.skipCacheCheck) ? true : OnyxCache_1.default.hasValueChanged(key, valueWithoutNestedNullValues);
201
208
  OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.SET, key, value, hasChanged);
202
209
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
203
- const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueWithoutNestedNullValues, hasChanged);
210
+ const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueWithoutNestedNullValues, hasChanged, isFromUpdate);
204
211
  // If the value has not changed or the key got removed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
205
212
  if (!hasChanged) {
206
213
  return updatePromise;
207
214
  }
208
215
  return storage_1.default.setItem(key, valueWithoutNestedNullValues)
209
- .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, set, key, valueWithoutNestedNullValues))
216
+ .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, setInternal, key, valueWithoutNestedNullValues, undefined, isFromUpdate))
210
217
  .then(() => {
211
218
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.SET, key, valueWithoutNestedNullValues);
212
219
  return updatePromise;
@@ -220,6 +227,13 @@ function set(key, value, options) {
220
227
  * @param data object keyed by ONYXKEYS and the values to set
221
228
  */
222
229
  function multiSet(data) {
230
+ return multiSetInternal(data);
231
+ }
232
+ /**
233
+ * @param isFromUpdate - Whether this call originates from Onyx.update()
234
+ * When isFromUpdate = true (called from Onyx.update()), useOnyx hook subscribers are batched to escape excessive re-renders in components
235
+ */
236
+ function multiSetInternal(data, isFromUpdate = false) {
223
237
  let newData = data;
224
238
  const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
225
239
  if (skippableCollectionMemberIDs.size) {
@@ -248,10 +262,10 @@ function multiSet(data) {
248
262
  }
249
263
  // Update cache and optimistically inform subscribers on the next tick
250
264
  OnyxCache_1.default.set(key, value);
251
- return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, prevValue);
265
+ return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, prevValue, undefined, isFromUpdate);
252
266
  });
253
267
  return storage_1.default.multiSet(keyValuePairsToSet)
254
- .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSet, newData))
268
+ .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSetInternal, newData, isFromUpdate))
255
269
  .then(() => {
256
270
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MULTI_SET, undefined, newData);
257
271
  return Promise.all(updatePromises);
@@ -275,6 +289,13 @@ function multiSet(data) {
275
289
  * Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
276
290
  */
277
291
  function merge(key, changes) {
292
+ return mergeInternal(key, changes);
293
+ }
294
+ /**
295
+ * @param isFromUpdate - Whether this call originates from Onyx.update()
296
+ * When isFromUpdate = true (called from Onyx.update()), useOnyx hook subscribers are batched to escape excessive re-renders in components
297
+ */
298
+ function mergeInternal(key, changes, isFromUpdate = false) {
278
299
  const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
279
300
  if (skippableCollectionMemberIDs.size) {
280
301
  try {
@@ -329,7 +350,7 @@ function merge(key, changes) {
329
350
  OnyxUtils_1.default.logKeyRemoved(OnyxUtils_1.default.METHOD.MERGE, key);
330
351
  return Promise.resolve();
331
352
  }
332
- return OnyxMerge_1.default.applyMerge(key, existingValue, validChanges).then(({ mergedValue, updatePromise }) => {
353
+ return OnyxMerge_1.default.applyMerge(key, existingValue, validChanges, isFromUpdate).then(({ mergedValue, updatePromise }) => {
333
354
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MERGE, key, changes, mergedValue);
334
355
  return updatePromise;
335
356
  });
@@ -355,7 +376,14 @@ function merge(key, changes) {
355
376
  * @param collection Object collection keyed by individual collection member keys and values
356
377
  */
357
378
  function mergeCollection(collectionKey, collection) {
358
- return OnyxUtils_1.default.mergeCollectionWithPatches(collectionKey, collection);
379
+ return mergeCollectionInternal(collectionKey, collection);
380
+ }
381
+ /**
382
+ * @param isFromUpdate - Whether this call originates from Onyx.update()
383
+ * When isFromUpdate = true (called from Onyx.update()), useOnyx hook subscribers are batched to escape excessive re-renders in components
384
+ */
385
+ function mergeCollectionInternal(collectionKey, collection, isFromUpdate = false) {
386
+ return OnyxUtils_1.default.mergeCollectionWithPatches(collectionKey, collection, undefined, isFromUpdate);
359
387
  }
360
388
  /**
361
389
  * Clear out all the data in the store
@@ -435,10 +463,10 @@ function clear(keysToPreserve = []) {
435
463
  const updatePromises = [];
436
464
  // Notify the subscribers for each key/value group so they can receive the new values
437
465
  Object.entries(keyValuesToResetIndividually).forEach(([key, value]) => {
438
- updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, OnyxCache_1.default.get(key, false)));
466
+ updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, OnyxCache_1.default.get(key, false), undefined, false));
439
467
  });
440
468
  Object.entries(keyValuesToResetAsCollection).forEach(([key, value]) => {
441
- updatePromises.push(OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(key, value));
469
+ updatePromises.push(OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(key, value, undefined, false));
442
470
  });
443
471
  const defaultKeyValuePairs = Object.entries(Object.keys(defaultKeyStates)
444
472
  .filter((key) => !keysToPreserve.includes(key))
@@ -522,7 +550,7 @@ function update(data) {
522
550
  collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]));
523
551
  }
524
552
  },
525
- [OnyxUtils_1.default.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v)),
553
+ [OnyxUtils_1.default.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollectionInternal(k, v, true)),
526
554
  [OnyxUtils_1.default.METHOD.MULTI_SET]: (k, v) => Object.entries(v).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue)),
527
555
  [OnyxUtils_1.default.METHOD.CLEAR]: () => {
528
556
  clearPromise = clear();
@@ -564,23 +592,23 @@ function update(data) {
564
592
  set: {},
565
593
  });
566
594
  if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
567
- promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches(collectionKey, batchedCollectionUpdates.merge, batchedCollectionUpdates.mergeReplaceNullPatches));
595
+ promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches(collectionKey, batchedCollectionUpdates.merge, batchedCollectionUpdates.mergeReplaceNullPatches, true));
568
596
  }
569
597
  if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
570
- promises.push(() => multiSet(batchedCollectionUpdates.set));
598
+ promises.push(() => multiSetInternal(batchedCollectionUpdates.set, true));
571
599
  }
572
600
  });
573
601
  Object.entries(updateQueue).forEach(([key, operations]) => {
574
602
  if (operations[0] === null) {
575
603
  const batchedChanges = OnyxUtils_1.default.mergeChanges(operations).result;
576
- promises.push(() => set(key, batchedChanges));
604
+ promises.push(() => setInternal(key, batchedChanges, undefined, true));
577
605
  return;
578
606
  }
579
607
  operations.forEach((operation) => {
580
- promises.push(() => merge(key, operation));
608
+ promises.push(() => mergeInternal(key, operation, true));
581
609
  });
582
610
  });
583
- const snapshotPromises = OnyxUtils_1.default.updateSnapshots(data, merge);
611
+ const snapshotPromises = OnyxUtils_1.default.updateSnapshots(data, (key, changes) => mergeInternal(key, changes, true));
584
612
  // We need to run the snapshot updates before the other updates so the snapshot data can be updated before the loading state in the snapshot
585
613
  const finalPromises = snapshotPromises.concat(promises);
586
614
  return clearPromise.then(() => Promise.all(finalPromises.map((p) => p()))).then(() => undefined);
@@ -599,6 +627,13 @@ function update(data) {
599
627
  * @param collection Object collection keyed by individual collection member keys and values
600
628
  */
601
629
  function setCollection(collectionKey, collection) {
630
+ return setCollectionInternal(collectionKey, collection);
631
+ }
632
+ /**
633
+ * @param isFromUpdate - Whether this call originates from Onyx.update()
634
+ * When isFromUpdate = true (called from Onyx.update()), useOnyx hook subscribers are batched to escape excessive re-renders in components
635
+ */
636
+ function setCollectionInternal(collectionKey, collection, isFromUpdate = false) {
602
637
  let resultCollection = collection;
603
638
  let resultCollectionKeys = Object.keys(resultCollection);
604
639
  // Confirm all the collection keys belong to the same parent
@@ -638,9 +673,9 @@ function setCollection(collectionKey, collection) {
638
673
  const keyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(mutableCollection, true);
639
674
  const previousCollection = OnyxUtils_1.default.getCachedCollection(collectionKey);
640
675
  keyValuePairs.forEach(([key, value]) => OnyxCache_1.default.set(key, value));
641
- const updatePromise = OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
676
+ const updatePromise = OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection, isFromUpdate);
642
677
  return storage_1.default.multiSet(keyValuePairs)
643
- .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, setCollection, collectionKey, collection))
678
+ .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, setCollectionInternal, collectionKey, collection, isFromUpdate))
644
679
  .then(() => {
645
680
  OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.SET_COLLECTION, undefined, mutableCollection);
646
681
  return updatePromise;
@@ -19,8 +19,8 @@ declare class OnyxCache {
19
19
  private recentKeys;
20
20
  /** A map of cached values */
21
21
  private storageMap;
22
- /** Index mapping collection keys to their member keys for O(1) lookup */
23
- private collectionIndex;
22
+ /** Cache of complete collection data objects for O(1) retrieval */
23
+ private collectionData;
24
24
  /**
25
25
  * Captured pending tasks for already running storage methods
26
26
  * Using a map yields better performance on operations such a delete
@@ -165,10 +165,6 @@ declare class OnyxCache {
165
165
  * Get all data for a collection key
166
166
  */
167
167
  getCollectionData(collectionKey: OnyxKey): Record<OnyxKey, OnyxValue<OnyxKey>> | undefined;
168
- /**
169
- * Get all member keys for a collection key
170
- */
171
- getCollectionMemberKeys(collectionKey: OnyxKey): Set<OnyxKey> | undefined;
172
168
  }
173
169
  declare const instance: OnyxCache;
174
170
  export default instance;
package/dist/OnyxCache.js CHANGED
@@ -68,10 +68,10 @@ class OnyxCache {
68
68
  this.nullishStorageKeys = new Set();
69
69
  this.recentKeys = new Set();
70
70
  this.storageMap = {};
71
- this.collectionIndex = {};
71
+ this.collectionData = {};
72
72
  this.pendingPromises = new Map();
73
73
  // bind all public methods to prevent problems with `this`
74
- (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'addToAccessedKeys', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys', 'setEvictionAllowList', 'getEvictionBlocklist', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'isCollectionKey', 'getCollectionKey', 'getCollectionData', 'getCollectionMemberKeys');
74
+ (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'addToAccessedKeys', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys', 'setEvictionAllowList', 'getEvictionBlocklist', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'isCollectionKey', 'getCollectionKey', 'getCollectionData');
75
75
  }
76
76
  /** Get all the storage keys */
77
77
  getAllKeys() {
@@ -136,33 +136,33 @@ class OnyxCache {
136
136
  const collectionKey = this.getCollectionKey(key);
137
137
  if (value === null || value === undefined) {
138
138
  delete this.storageMap[key];
139
- // Remove from collection index if it's a collection member
140
- if (collectionKey && this.getCollectionMemberKeys(collectionKey)) {
141
- this.collectionIndex[collectionKey].delete(key);
139
+ // Remove from collection data cache if it's a collection member
140
+ if (collectionKey && this.collectionData[collectionKey]) {
141
+ delete this.collectionData[collectionKey][key];
142
142
  }
143
143
  return undefined;
144
144
  }
145
145
  this.storageMap[key] = value;
146
- // Update collection index if this is a collection member
146
+ // Update collection data cache if this is a collection member
147
147
  if (collectionKey) {
148
- if (!this.getCollectionMemberKeys(collectionKey)) {
149
- this.collectionIndex[collectionKey] = new Set();
148
+ if (!this.collectionData[collectionKey]) {
149
+ this.collectionData[collectionKey] = {};
150
150
  }
151
- this.collectionIndex[collectionKey].add(key);
151
+ this.collectionData[collectionKey][key] = value;
152
152
  }
153
153
  return value;
154
154
  }
155
155
  /** Forget the cached value for the given key */
156
156
  drop(key) {
157
157
  delete this.storageMap[key];
158
- // Update collection index if this is a collection member
158
+ // Remove from collection data cache if this is a collection member
159
159
  const collectionKey = this.getCollectionKey(key);
160
- if (collectionKey && this.getCollectionMemberKeys(collectionKey)) {
161
- this.collectionIndex[collectionKey].delete(key);
160
+ if (collectionKey && this.collectionData[collectionKey]) {
161
+ delete this.collectionData[collectionKey][key];
162
162
  }
163
- // If this is a collection key, clear its index
163
+ // If this is a collection key, clear its data
164
164
  if (this.isCollectionKey(key)) {
165
- delete this.collectionIndex[key];
165
+ delete this.collectionData[key];
166
166
  }
167
167
  this.storageKeys.delete(key);
168
168
  this.recentKeys.delete(key);
@@ -185,19 +185,19 @@ class OnyxCache {
185
185
  const collectionKey = this.getCollectionKey(key);
186
186
  if (value === null || value === undefined) {
187
187
  this.addNullishStorageKey(key);
188
- // Remove from collection index if it's a collection member
189
- if (collectionKey && this.getCollectionMemberKeys(collectionKey)) {
190
- this.collectionIndex[collectionKey].delete(key);
188
+ // Remove from collection data cache if it's a collection member
189
+ if (collectionKey && this.collectionData[collectionKey]) {
190
+ delete this.collectionData[collectionKey][key];
191
191
  }
192
192
  }
193
193
  else {
194
194
  this.nullishStorageKeys.delete(key);
195
- // Update collection index if this is a collection member
195
+ // Update collection data cache if this is a collection member
196
196
  if (collectionKey) {
197
- if (!this.getCollectionMemberKeys(collectionKey)) {
198
- this.collectionIndex[collectionKey] = new Set();
197
+ if (!this.collectionData[collectionKey]) {
198
+ this.collectionData[collectionKey] = {};
199
199
  }
200
- this.collectionIndex[collectionKey].add(key);
200
+ this.collectionData[collectionKey][key] = this.storageMap[key];
201
201
  }
202
202
  }
203
203
  });
@@ -257,10 +257,10 @@ class OnyxCache {
257
257
  }
258
258
  for (const key of keysToRemove) {
259
259
  delete this.storageMap[key];
260
- // Update collection index if this is a collection member
260
+ // Remove from collection data cache if this is a collection member
261
261
  const collectionKey = this.getCollectionKey(key);
262
- if (collectionKey && this.getCollectionMemberKeys(collectionKey)) {
263
- this.collectionIndex[collectionKey].delete(key);
262
+ if (collectionKey && this.collectionData[collectionKey]) {
263
+ delete this.collectionData[collectionKey][key];
264
264
  }
265
265
  this.recentKeys.delete(key);
266
266
  }
@@ -358,12 +358,12 @@ class OnyxCache {
358
358
  */
359
359
  setCollectionKeys(collectionKeys) {
360
360
  this.collectionKeys = collectionKeys;
361
- // Initialize collection indexes for existing collection keys
361
+ // Initialize collection data for existing collection keys
362
362
  collectionKeys.forEach((collectionKey) => {
363
- if (this.getCollectionMemberKeys(collectionKey)) {
363
+ if (this.collectionData[collectionKey]) {
364
364
  return;
365
365
  }
366
- this.collectionIndex[collectionKey] = new Set();
366
+ this.collectionData[collectionKey] = {};
367
367
  });
368
368
  }
369
369
  /**
@@ -387,24 +387,12 @@ class OnyxCache {
387
387
  * Get all data for a collection key
388
388
  */
389
389
  getCollectionData(collectionKey) {
390
- const memberKeys = this.getCollectionMemberKeys(collectionKey);
391
- if (!memberKeys || memberKeys.size === 0) {
390
+ const cachedCollection = this.collectionData[collectionKey];
391
+ if (!cachedCollection || Object.keys(cachedCollection).length === 0) {
392
392
  return undefined;
393
393
  }
394
- const collectionData = {};
395
- memberKeys.forEach((memberKey) => {
396
- const value = this.storageMap[memberKey];
397
- if (value !== undefined) {
398
- collectionData[memberKey] = value;
399
- }
400
- });
401
- return collectionData;
402
- }
403
- /**
404
- * Get all member keys for a collection key
405
- */
406
- getCollectionMemberKeys(collectionKey) {
407
- return this.collectionIndex[collectionKey];
394
+ // Return a shallow copy to ensure React detects changes when items are added/removed
395
+ return Object.assign({}, cachedCollection);
408
396
  }
409
397
  }
410
398
  const instance = new OnyxCache();
@@ -6,14 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const OnyxCache_1 = __importDefault(require("../OnyxCache"));
7
7
  const OnyxUtils_1 = __importDefault(require("../OnyxUtils"));
8
8
  const storage_1 = __importDefault(require("../storage"));
9
- const applyMerge = (key, existingValue, validChanges) => {
9
+ const applyMerge = (key, existingValue, validChanges, isFromUpdate = false) => {
10
10
  const { result: mergedValue } = OnyxUtils_1.default.mergeChanges(validChanges, existingValue);
11
11
  // In cache, we don't want to remove the key if it's null to improve performance and speed up the next merge.
12
12
  const hasChanged = OnyxCache_1.default.hasValueChanged(key, mergedValue);
13
13
  // Logging properties only since values could be sensitive things we don't want to log.
14
14
  OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.MERGE, key, mergedValue, hasChanged);
15
15
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
16
- const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged);
16
+ const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged, isFromUpdate);
17
17
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
18
18
  if (!hasChanged) {
19
19
  return Promise.resolve({ mergedValue, updatePromise });
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const OnyxUtils_1 = __importDefault(require("../OnyxUtils"));
7
7
  const OnyxCache_1 = __importDefault(require("../OnyxCache"));
8
8
  const storage_1 = __importDefault(require("../storage"));
9
- const applyMerge = (key, existingValue, validChanges) => {
9
+ const applyMerge = (key, existingValue, validChanges, isFromUpdate = false) => {
10
10
  // If any of the changes is null, we need to discard the existing value.
11
11
  const baseValue = validChanges.includes(null) ? undefined : existingValue;
12
12
  // We first batch the changes into a single change with object removal marks,
@@ -19,7 +19,7 @@ const applyMerge = (key, existingValue, validChanges) => {
19
19
  // Logging properties only since values could be sensitive things we don't want to log.
20
20
  OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.MERGE, key, mergedValue, hasChanged);
21
21
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
22
- const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged);
22
+ const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged, isFromUpdate);
23
23
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
24
24
  if (!hasChanged) {
25
25
  return Promise.resolve({ mergedValue, updatePromise });
@@ -3,5 +3,5 @@ type ApplyMergeResult<TValue> = {
3
3
  mergedValue: TValue;
4
4
  updatePromise: Promise<void>;
5
5
  };
6
- type ApplyMerge = <TKey extends OnyxKey, TValue extends OnyxInput<OnyxKey> | undefined, TChange extends OnyxInput<OnyxKey> | null>(key: TKey, existingValue: TValue, validChanges: TChange[]) => Promise<ApplyMergeResult<TChange>>;
6
+ type ApplyMerge = <TKey extends OnyxKey, TValue extends OnyxInput<OnyxKey> | undefined, TChange extends OnyxInput<OnyxKey> | null>(key: TKey, existingValue: TValue, validChanges: TChange[], isFromUpdate?: boolean) => Promise<ApplyMergeResult<TChange>>;
7
7
  export type { ApplyMerge, ApplyMergeResult };
@@ -143,14 +143,14 @@ declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionK
143
143
  /**
144
144
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
145
145
  */
146
- declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean): void;
146
+ declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean, notifyUseOnyxHookSubscribers?: boolean): void;
147
147
  /**
148
148
  * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
149
149
  *
150
150
  * @example
151
151
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
152
152
  */
153
- declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean): void;
153
+ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyConnectSubscribers?: boolean, notifyWithOnyxSubscribers?: boolean, notifyUseOnyxHookSubscribers?: boolean): void;
154
154
  /**
155
155
  * Sends the data obtained from the keys to the connection. It either:
156
156
  * - sets state on the withOnyxInstances
@@ -172,13 +172,13 @@ declare function getCollectionDataAndSendAsObject<TKey extends OnyxKey>(matching
172
172
  * @example
173
173
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
174
174
  */
175
- declare function scheduleSubscriberUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean): Promise<void>;
175
+ declare function scheduleSubscriberUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, isFromUpdate?: boolean): Promise<void>;
176
176
  /**
177
177
  * This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
178
178
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
179
179
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
180
180
  */
181
- declare function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(key: TKey, value: OnyxCollection<KeyValueMapping[TKey]>, previousValue?: OnyxCollection<KeyValueMapping[TKey]>): Promise<void>;
181
+ declare function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(key: TKey, value: OnyxCollection<KeyValueMapping[TKey]>, previousValue?: OnyxCollection<KeyValueMapping[TKey]>, isFromUpdate?: boolean): Promise<void>;
182
182
  /**
183
183
  * Remove a key from Onyx and update the subscribers
184
184
  */
@@ -193,7 +193,7 @@ declare function evictStorageAndRetry<TMethod extends typeof Onyx.set | typeof O
193
193
  /**
194
194
  * Notifies subscribers and writes current value to cache
195
195
  */
196
- declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean): Promise<void>;
196
+ declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean, isFromUpdate?: boolean): Promise<void>;
197
197
  declare function hasPendingMergeForKey(key: OnyxKey): boolean;
198
198
  /**
199
199
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
@@ -254,7 +254,7 @@ declare function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge)
254
254
  * @param mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of
255
255
  * tuples that we'll use to replace the nested objects of that collection member record with something else.
256
256
  */
257
- declare function mergeCollectionWithPatches<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>, mergeReplaceNullPatches?: MultiMergeReplaceNullPatches): Promise<void>;
257
+ declare function mergeCollectionWithPatches<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>, mergeReplaceNullPatches?: MultiMergeReplaceNullPatches, isFromUpdate?: boolean): Promise<void>;
258
258
  declare function logKeyChanged(onyxMethod: Extract<OnyxMethod, 'set' | 'merge'>, key: OnyxKey, value: unknown, hasChanged: boolean): void;
259
259
  declare function logKeyRemoved(onyxMethod: Extract<OnyxMethod, 'set' | 'merge'>, key: OnyxKey): void;
260
260
  /**
package/dist/OnyxUtils.js CHANGED
@@ -440,25 +440,16 @@ function tryGetCachedValue(key, mapping) {
440
440
  let val = OnyxCache_1.default.get(key);
441
441
  if (isCollectionKey(key)) {
442
442
  const collectionData = OnyxCache_1.default.getCollectionData(key);
443
- const allCacheKeys = OnyxCache_1.default.getAllKeys();
444
- if (collectionData !== undefined && allCacheKeys.size > 0) {
443
+ if (collectionData !== undefined) {
445
444
  val = collectionData;
446
445
  }
447
446
  else {
448
- // Fallback to original logic
449
- // It is possible we haven't loaded all keys yet so we do not know if the
450
- // collection actually exists.
451
- if (allCacheKeys.size === 0) {
447
+ // If we haven't loaded all keys yet, we can't determine if the collection exists
448
+ if (OnyxCache_1.default.getAllKeys().size === 0) {
452
449
  return;
453
450
  }
454
- const values = {};
455
- allCacheKeys.forEach((cacheKey) => {
456
- if (!cacheKey.startsWith(key)) {
457
- return;
458
- }
459
- values[cacheKey] = OnyxCache_1.default.get(cacheKey);
460
- });
461
- val = values;
451
+ // Set an empty collection object for collections that exist but have no data
452
+ val = {};
462
453
  }
463
454
  }
464
455
  if (mapping === null || mapping === void 0 ? void 0 : mapping.selector) {
@@ -512,7 +503,7 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
512
503
  /**
513
504
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
514
505
  */
515
- function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true) {
506
+ function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true, notifyUseOnyxHookSubscribers = true) {
516
507
  // We prepare the "cached collection" which is the entire collection + the new partial data that
517
508
  // was merged in via mergeCollection().
518
509
  const cachedCollection = getCachedCollection(collectionKey);
@@ -540,7 +531,8 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
540
531
  const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
541
532
  // Regular Onyx.connect() subscriber found.
542
533
  if (typeof subscriber.callback === 'function') {
543
- if (!notifyConnectSubscribers) {
534
+ // Check if it's a useOnyx or a regular Onyx.connect() subscriber
535
+ if ((subscriber.isUseOnyxSubscriber && !notifyUseOnyxHookSubscribers) || (!subscriber.isUseOnyxSubscriber && !notifyConnectSubscribers)) {
544
536
  continue;
545
537
  }
546
538
  // If they are subscribed to the collection key and using waitForCollectionCallback then we'll
@@ -670,7 +662,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
670
662
  * @example
671
663
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
672
664
  */
673
- function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true) {
665
+ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true, notifyConnectSubscribers = true, notifyWithOnyxSubscribers = true, notifyUseOnyxHookSubscribers = true) {
674
666
  var _a, _b;
675
667
  // Add or remove this key from the recentlyAccessedKeys lists
676
668
  if (value !== null) {
@@ -709,7 +701,8 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
709
701
  }
710
702
  // Subscriber is a regular call to connect() and provided a callback
711
703
  if (typeof subscriber.callback === 'function') {
712
- if (!notifyConnectSubscribers) {
704
+ // Check if it's a useOnyx or a regular Onyx.connect() subscriber
705
+ if ((subscriber.isUseOnyxSubscriber && !notifyUseOnyxHookSubscribers) || (!subscriber.isUseOnyxSubscriber && !notifyConnectSubscribers)) {
713
706
  continue;
714
707
  }
715
708
  if (lastConnectionCallbackData.has(subscriber.subscriptionID) && lastConnectionCallbackData.get(subscriber.subscriptionID) === value) {
@@ -888,9 +881,9 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
888
881
  * @example
889
882
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
890
883
  */
891
- function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber = () => true) {
892
- const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false));
893
- batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true));
884
+ function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber = () => true, isFromUpdate = false) {
885
+ const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false, !isFromUpdate));
886
+ batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true, isFromUpdate));
894
887
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
895
888
  }
896
889
  /**
@@ -898,9 +891,9 @@ function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber
898
891
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
899
892
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
900
893
  */
901
- function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
902
- const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false));
903
- batchUpdates(() => keysChanged(key, value, previousValue, false, true));
894
+ function scheduleNotifyCollectionSubscribers(key, value, previousValue, isFromUpdate = false) {
895
+ const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false, !isFromUpdate));
896
+ batchUpdates(() => keysChanged(key, value, previousValue, false, true, isFromUpdate));
904
897
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
905
898
  }
906
899
  /**
@@ -950,7 +943,7 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
950
943
  /**
951
944
  * Notifies subscribers and writes current value to cache
952
945
  */
953
- function broadcastUpdate(key, value, hasChanged) {
946
+ function broadcastUpdate(key, value, hasChanged, isFromUpdate = false) {
954
947
  const prevValue = OnyxCache_1.default.get(key, false);
955
948
  // Update subscribers if the cached value has changed, or when the subscriber specifically requires
956
949
  // all updates regardless of value changes (indicated by initWithStoredValues set to false).
@@ -960,7 +953,7 @@ function broadcastUpdate(key, value, hasChanged) {
960
953
  else {
961
954
  OnyxCache_1.default.addToAccessedKeys(key);
962
955
  }
963
- return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || (subscriber === null || subscriber === void 0 ? void 0 : subscriber.initWithStoredValues) === false).then(() => undefined);
956
+ return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || (subscriber === null || subscriber === void 0 ? void 0 : subscriber.initWithStoredValues) === false, isFromUpdate).then(() => undefined);
964
957
  }
965
958
  function hasPendingMergeForKey(key) {
966
959
  return !!mergeQueue[key];
@@ -1238,7 +1231,7 @@ function updateSnapshots(data, mergeFn) {
1238
1231
  * @param mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of
1239
1232
  * tuples that we'll use to replace the nested objects of that collection member record with something else.
1240
1233
  */
1241
- function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches) {
1234
+ function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches, isFromUpdate = false) {
1242
1235
  if (!isValidNonEmptyCollectionForMerge(collection)) {
1243
1236
  Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
1244
1237
  return Promise.resolve();
@@ -1320,7 +1313,7 @@ function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullP
1320
1313
  // and update all subscribers
1321
1314
  const promiseUpdate = previousCollectionPromise.then((previousCollection) => {
1322
1315
  OnyxCache_1.default.merge(finalMergedCollection);
1323
- return scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection);
1316
+ return scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection, isFromUpdate);
1324
1317
  });
1325
1318
  return Promise.all(promises)
1326
1319
  .catch((error) => evictStorageAndRetry(error, mergeCollectionWithPatches, collectionKey, resultCollection))
package/dist/types.d.ts CHANGED
@@ -235,6 +235,8 @@ type BaseConnectOptions = {
235
235
  * with the same connect configurations.
236
236
  */
237
237
  reuseConnection?: boolean;
238
+ /** Indicates whether this subscriber is created from the useOnyx hook. */
239
+ isUseOnyxSubscriber?: boolean;
238
240
  };
239
241
  /** Represents the callback function used in `Onyx.connect()` method with a regular key. */
240
242
  type DefaultConnectCallback<TKey extends OnyxKey> = (value: OnyxEntry<KeyValueMapping[TKey]>, key: TKey) => void;
package/dist/useOnyx.js CHANGED
@@ -276,6 +276,7 @@ function useOnyx(key, options, dependencies = []) {
276
276
  initWithStoredValues: options === null || options === void 0 ? void 0 : options.initWithStoredValues,
277
277
  waitForCollectionCallback: OnyxUtils_1.default.isCollectionKey(key),
278
278
  reuseConnection: options === null || options === void 0 ? void 0 : options.reuseConnection,
279
+ isUseOnyxSubscriber: true,
279
280
  });
280
281
  checkEvictableKey();
281
282
  return () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.137",
3
+ "version": "2.0.139",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",