react-native-onyx 3.0.21 → 3.0.23

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
@@ -60,7 +60,11 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedK
60
60
  if (shouldSyncMultipleInstances) {
61
61
  (_a = storage_1.default.keepInstancesSync) === null || _a === void 0 ? void 0 : _a.call(storage_1.default, (key, value) => {
62
62
  OnyxCache_1.default.set(key, value);
63
- OnyxUtils_1.default.keyChanged(key, value);
63
+ // Check if this is a collection member key to prevent duplicate callbacks
64
+ // When a collection is updated, individual members sync separately to other tabs
65
+ // Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
66
+ const isKeyCollectionMember = OnyxUtils_1.default.isCollectionMember(key);
67
+ OnyxUtils_1.default.keyChanged(key, value, undefined, true, isKeyCollectionMember);
64
68
  });
65
69
  }
66
70
  if (maxCachedKeysCount > 0) {
@@ -107,6 +107,12 @@ declare function getCollectionKeys(): Set<OnyxKey>;
107
107
  */
108
108
  declare function isCollectionKey(key: OnyxKey): key is CollectionKeyBase;
109
109
  declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collectionKey: TCollectionKey, key: string): key is `${TCollectionKey}${string}`;
110
+ /**
111
+ * Checks if a given key is a collection member key (not just a collection key).
112
+ * @param key - The key to check
113
+ * @returns true if the key is a collection member, false otherwise
114
+ */
115
+ declare function isCollectionMember(key: OnyxKey): boolean;
110
116
  /**
111
117
  * Splits a collection member key into the collection key part and the ID part.
112
118
  * @param key - The collection member key to split.
@@ -328,6 +334,7 @@ declare const OnyxUtils: {
328
334
  getCollectionKeys: typeof getCollectionKeys;
329
335
  isCollectionKey: typeof isCollectionKey;
330
336
  isCollectionMemberKey: typeof isCollectionMemberKey;
337
+ isCollectionMember: typeof isCollectionMember;
331
338
  splitCollectionMemberKey: typeof splitCollectionMemberKey;
332
339
  isKeyMatch: typeof isKeyMatch;
333
340
  tryGetCachedValue: typeof tryGetCachedValue;
package/dist/OnyxUtils.js CHANGED
@@ -391,6 +391,22 @@ function isCollectionKey(key) {
391
391
  function isCollectionMemberKey(collectionKey, key) {
392
392
  return key.startsWith(collectionKey) && key.length > collectionKey.length;
393
393
  }
394
+ /**
395
+ * Checks if a given key is a collection member key (not just a collection key).
396
+ * @param key - The key to check
397
+ * @returns true if the key is a collection member, false otherwise
398
+ */
399
+ function isCollectionMember(key) {
400
+ try {
401
+ const collectionKey = getCollectionKey(key);
402
+ // If the key is longer than the collection key, it's a collection member
403
+ return key.length > collectionKey.length;
404
+ }
405
+ catch (e) {
406
+ // If getCollectionKey throws, the key is not a collection member
407
+ return false;
408
+ }
409
+ }
394
410
  /**
395
411
  * Splits a collection member key into the collection key part and the ID part.
396
412
  * @param key - The collection member key to split.
@@ -465,81 +481,6 @@ function tryGetCachedValue(key) {
465
481
  }
466
482
  return val;
467
483
  }
468
- /**
469
- * Marks items that existed in previousCollection but not in preservedCollection as null.
470
- * This ensures subscribers are properly notified about item removals.
471
- * @param preservedCollection - The collection to mark removed items in (mutated in place)
472
- * @param previousCollection - The previous collection state to compare against
473
- */
474
- function markRemovedItemsAsNull(preservedCollection, previousCollection) {
475
- if (!previousCollection) {
476
- return preservedCollection;
477
- }
478
- const mutablePreservedCollection = Object.assign({}, preservedCollection);
479
- Object.keys(previousCollection).forEach((key) => {
480
- if (key in preservedCollection) {
481
- return;
482
- }
483
- mutablePreservedCollection[key] = null;
484
- });
485
- return mutablePreservedCollection;
486
- }
487
- /**
488
- * Utility function to preserve object references for unchanged items in collection operations.
489
- * Compares new values with cached values using deep equality and preserves references when data is identical.
490
- * @param keyValuePairs - Array of key-value pairs to process
491
- * @param previousCollection - Optional previous collection state. If provided, removed items will be included as null
492
- * @returns The preserved collection with unchanged references maintained and removed items marked as null
493
- */
494
- function preserveCollectionReferences(keyValuePairs, previousCollection) {
495
- const preservedCollection = {};
496
- keyValuePairs.forEach(([key, value]) => {
497
- const cachedValue = OnyxCache_1.default.get(key, false);
498
- // If no cached value exists, we need to add the new value (skip expensive deep equality check)
499
- // Use deep equality check to preserve references for unchanged items
500
- if (cachedValue !== undefined && (0, fast_equals_1.deepEqual)(value, cachedValue)) {
501
- // Keep the existing reference
502
- preservedCollection[key] = cachedValue;
503
- }
504
- else {
505
- // Update cache only for changed items
506
- OnyxCache_1.default.set(key, value);
507
- preservedCollection[key] = value;
508
- }
509
- });
510
- if (previousCollection) {
511
- return markRemovedItemsAsNull(preservedCollection, previousCollection);
512
- }
513
- return preservedCollection;
514
- }
515
- /**
516
- * Utility function for merge operations that preserves references after cache merge has been performed.
517
- * Compares merged values with original cached values and preserves references when data is unchanged.
518
- * @param collection - Collection of merged data
519
- * @param originalCachedValues - Original cached values before merge
520
- * @param previousCollection - Optional previous collection state. If provided, removed items will be included as null
521
- * @returns The preserved collection with unchanged references maintained and removed items marked as null
522
- */
523
- function preserveCollectionReferencesAfterMerge(collection, originalCachedValues, previousCollection) {
524
- const preservedCollection = {};
525
- Object.keys(collection).forEach((key) => {
526
- const newMergedValue = OnyxCache_1.default.get(key, false);
527
- const originalValue = originalCachedValues[key];
528
- // Use deep equality check to preserve references for unchanged items
529
- if (originalValue !== undefined && (0, fast_equals_1.deepEqual)(newMergedValue, originalValue)) {
530
- // Keep the existing reference and update cache
531
- preservedCollection[key] = originalValue;
532
- OnyxCache_1.default.set(key, originalValue);
533
- }
534
- else {
535
- preservedCollection[key] = newMergedValue;
536
- }
537
- });
538
- if (previousCollection) {
539
- return markRemovedItemsAsNull(preservedCollection, previousCollection);
540
- }
541
- return preservedCollection;
542
- }
543
484
  function getCachedCollection(collectionKey, collectionMemberKeys) {
544
485
  // Use optimized collection data retrieval when cache is populated
545
486
  const collectionData = OnyxCache_1.default.getCollectionData(collectionKey);
@@ -1270,9 +1211,8 @@ function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
1270
1211
  });
1271
1212
  const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
1272
1213
  const previousCollection = OnyxUtils.getCachedCollection(collectionKey);
1273
- // Preserve references for unchanged items and include removed items as null in setCollection
1274
- const preservedCollection = preserveCollectionReferences(keyValuePairs, previousCollection);
1275
- const updatePromise = OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
1214
+ keyValuePairs.forEach(([key, value]) => OnyxCache_1.default.set(key, value));
1215
+ const updatePromise = OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
1276
1216
  return storage_1.default.multiSet(keyValuePairs)
1277
1217
  .catch((error) => OnyxUtils.retryOperation(error, setCollectionWithRetry, { collectionKey, collection }, retryAttempt))
1278
1218
  .then(() => {
@@ -1324,8 +1264,6 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1324
1264
  resultCollectionKeys = Object.keys(resultCollection);
1325
1265
  return getAllKeys()
1326
1266
  .then((persistedKeys) => {
1327
- // Capture keys that will be removed (before calling remove())
1328
- const keysToRemove = resultCollectionKeys.filter((key) => resultCollection[key] === null && persistedKeys.has(key));
1329
1267
  // Split to keys that exist in storage and keys that don't
1330
1268
  const keys = resultCollectionKeys.filter((key) => {
1331
1269
  if (resultCollection[key] === null) {
@@ -1335,8 +1273,6 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1335
1273
  return true;
1336
1274
  });
1337
1275
  const existingKeys = keys.filter((key) => persistedKeys.has(key));
1338
- // Get previous values for both existing keys and keys that will be removed
1339
- const allAffectedKeys = [...existingKeys, ...keysToRemove];
1340
1276
  const cachedCollectionForExistingKeys = getCachedCollection(collectionKey, existingKeys);
1341
1277
  const existingKeyCollection = existingKeys.reduce((obj, key) => {
1342
1278
  const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(resultCollection[key], cachedCollectionForExistingKeys[key]);
@@ -1365,8 +1301,7 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1365
1301
  const promises = [];
1366
1302
  // We need to get the previously existing values so we can compare the new ones
1367
1303
  // against them, to avoid unnecessary subscriber updates.
1368
- // Include keys that will be removed so subscribers are notified about removals
1369
- const previousCollectionPromise = Promise.all(allAffectedKeys.map((key) => get(key).then((value) => [key, value]))).then(Object.fromEntries);
1304
+ const previousCollectionPromise = Promise.all(existingKeys.map((key) => get(key).then((value) => [key, value]))).then(Object.fromEntries);
1370
1305
  // New keys will be added via multiSet while existing keys will be updated using multiMerge
1371
1306
  // This is because setting a key that doesn't exist yet with multiMerge will throw errors
1372
1307
  if (keyValuePairsForExistingCollection.length > 0) {
@@ -1380,16 +1315,8 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1380
1315
  // Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
1381
1316
  // and update all subscribers
1382
1317
  const promiseUpdate = previousCollectionPromise.then((previousCollection) => {
1383
- // Capture the original cached values before merging
1384
- const originalCachedValues = {};
1385
- Object.keys(finalMergedCollection).forEach((key) => {
1386
- originalCachedValues[key] = OnyxCache_1.default.get(key, false);
1387
- });
1388
- // Then merge all the data into cache as normal
1389
1318
  OnyxCache_1.default.merge(finalMergedCollection);
1390
- // Finally, preserve references for items that didn't actually change and include removed items as null
1391
- const preservedCollection = preserveCollectionReferencesAfterMerge(finalMergedCollection, originalCachedValues, previousCollection);
1392
- return scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
1319
+ return scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection);
1393
1320
  });
1394
1321
  return Promise.all(promises)
1395
1322
  .catch((error) => retryOperation(error, mergeCollectionWithPatches, { collectionKey, collection: resultCollection, mergeReplaceNullPatches, isProcessingCollectionUpdate }, retryAttempt))
@@ -1440,9 +1367,8 @@ function partialSetCollection({ collectionKey, collection }, retryAttempt) {
1440
1367
  const existingKeys = resultCollectionKeys.filter((key) => persistedKeys.has(key));
1441
1368
  const previousCollection = getCachedCollection(collectionKey, existingKeys);
1442
1369
  const keyValuePairs = prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
1443
- // Preserve references for unchanged items and include removed items as null in partialSetCollection
1444
- const preservedCollection = preserveCollectionReferences(keyValuePairs, previousCollection);
1445
- const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
1370
+ keyValuePairs.forEach(([key, value]) => OnyxCache_1.default.set(key, value));
1371
+ const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
1446
1372
  return storage_1.default.multiSet(keyValuePairs)
1447
1373
  .catch((error) => retryOperation(error, partialSetCollection, { collectionKey, collection }, retryAttempt))
1448
1374
  .then(() => {
@@ -1483,6 +1409,7 @@ const OnyxUtils = {
1483
1409
  getCollectionKeys,
1484
1410
  isCollectionKey,
1485
1411
  isCollectionMemberKey,
1412
+ isCollectionMember,
1486
1413
  splitCollectionMemberKey,
1487
1414
  isKeyMatch,
1488
1415
  tryGetCachedValue,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.21",
3
+ "version": "3.0.23",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",