react-native-onyx 3.0.70 → 3.0.72

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/OnyxUtils.js CHANGED
@@ -454,6 +454,16 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
454
454
  const cachedCollection = getCachedCollection(collectionKey);
455
455
  const previousCollection = partialPreviousCollection !== null && partialPreviousCollection !== void 0 ? partialPreviousCollection : {};
456
456
  const changedMemberKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
457
+ // Add or remove the keys from the recentlyAccessedKeys list
458
+ for (const memberKey of changedMemberKeys) {
459
+ const value = partialCollection === null || partialCollection === void 0 ? void 0 : partialCollection[memberKey];
460
+ if (value !== null && value !== undefined) {
461
+ OnyxCache_1.default.addLastAccessedKey(memberKey, false);
462
+ }
463
+ else {
464
+ OnyxCache_1.default.removeLastAccessedKey(memberKey);
465
+ }
466
+ }
457
467
  // Use indexed lookup instead of scanning all subscribers.
458
468
  // We need subscribers for: (1) the collection key itself, and (2) individual changed member keys.
459
469
  const collectionSubscriberIDs = (_a = onyxKeyToSubscriptionIDs.get(collectionKey)) !== null && _a !== void 0 ? _a : [];
@@ -478,12 +488,20 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
478
488
  subscriber.callback(cachedCollection, subscriber.key, partialCollection);
479
489
  continue;
480
490
  }
481
- // Not using waitForCollectionCallback — notify per changed key
491
+ // Not using waitForCollectionCallback — notify per changed key.
492
+ // Re-check the subscription on each iteration because the callback may
493
+ // synchronously disconnect itself (removing it from callbackToStateMapping),
494
+ // in which case we must stop firing further callbacks for this subscriber.
482
495
  for (const dataKey of changedMemberKeys) {
496
+ const currentSubscriber = callbackToStateMapping[subID];
497
+ if (!currentSubscriber || typeof currentSubscriber.callback !== 'function') {
498
+ break;
499
+ }
483
500
  if (cachedCollection[dataKey] === previousCollection[dataKey]) {
484
501
  continue;
485
502
  }
486
- subscriber.callback(cachedCollection[dataKey], dataKey);
503
+ const currentSubscriberCallback = currentSubscriber.callback;
504
+ currentSubscriberCallback(cachedCollection[dataKey], dataKey);
487
505
  }
488
506
  }
489
507
  catch (error) {
@@ -1135,15 +1153,43 @@ function multiSetWithRetry(data, retryAttempt) {
1135
1153
  }, {});
1136
1154
  }
1137
1155
  const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);
1156
+ // Group collection members by their parent collection key so each collection can be notified
1157
+ // via a single batched keysChanged() call instead of one keyChanged() per member. For each
1158
+ // collection, `partial` holds the new values being set and `previous` holds the cached values
1159
+ // from before the set, which keysChanged() uses to skip subscribers whose value didn't change.
1160
+ const collectionBatches = new Map();
1138
1161
  for (const [key, value] of keyValuePairsToSet) {
1139
1162
  // When we use multiSet to set a key we want to clear the current delta changes from Onyx.merge that were queued
1140
1163
  // before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
1141
1164
  if (OnyxUtils.hasPendingMergeForKey(key)) {
1142
1165
  delete OnyxUtils.getMergeQueue()[key];
1143
1166
  }
1144
- // Update cache and optimistically inform subscribers
1145
- OnyxCache_1.default.set(key, value);
1146
- keyChanged(key, value);
1167
+ const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
1168
+ if (collectionKey && OnyxKeys_1.default.isCollectionMemberKey(collectionKey, key)) {
1169
+ // Capture the previous cached value BEFORE calling cache.set() so keysChanged()
1170
+ // can diff old vs new per-member.
1171
+ const previousValue = OnyxCache_1.default.get(key);
1172
+ OnyxCache_1.default.set(key, value);
1173
+ let batch = collectionBatches.get(collectionKey);
1174
+ if (!batch) {
1175
+ batch = { partial: {}, previous: {} };
1176
+ collectionBatches.set(collectionKey, batch);
1177
+ }
1178
+ batch.partial[key] = value;
1179
+ batch.previous[key] = previousValue;
1180
+ }
1181
+ else {
1182
+ // Non-collection keys are notified inline (cache.set + keyChanged in iteration order)
1183
+ // so re-entrant callbacks (e.g. Onyx.set inside a callback) see consistent cache
1184
+ // and subscriber state, matching the original per-key notification semantics.
1185
+ OnyxCache_1.default.set(key, value);
1186
+ keyChanged(key, value);
1187
+ }
1188
+ }
1189
+ // One keysChanged() per collection — fires each collection-level subscriber once and lets
1190
+ // keysChanged() internally decide which individual member subscribers need notification.
1191
+ for (const [collectionKey, batch] of collectionBatches) {
1192
+ keysChanged(collectionKey, batch.partial, batch.previous);
1147
1193
  }
1148
1194
  const keyValuePairsToStore = keyValuePairsToSet.filter((keyValuePair) => {
1149
1195
  const [key] = keyValuePair;
package/dist/useOnyx.js CHANGED
@@ -96,6 +96,9 @@ function useOnyx(key, options, dependencies = []) {
96
96
  // after cleanup), so the hook automatically enters first-connection mode for the new key without any
97
97
  // explicit reset logic — eliminating the race condition where cleanup could clobber a boolean flag.
98
98
  const connectedKeyRef = (0, react_1.useRef)(null);
99
+ // Tracks whether the hook has completed its initial mount subscription.
100
+ // Unlike connectedKeyRef (which gets nulled by cleanup), this persists across re-subscriptions.
101
+ const hasMountedRef = (0, react_1.useRef)(false);
99
102
  // Indicates if the hook is connecting to an Onyx key.
100
103
  const isConnectingRef = (0, react_1.useRef)(false);
101
104
  // Stores the `onStoreChange()` function, which can be used to trigger a `getSnapshot()` update when desired.
@@ -211,11 +214,19 @@ function useOnyx(key, options, dependencies = []) {
211
214
  const subscribe = (0, react_1.useCallback)((onStoreChange) => {
212
215
  // Reset internal state so the hook properly transitions through loading
213
216
  // for the new key instead of preserving stale state from the previous one.
214
- previousValueRef.current = null;
215
- newValueRef.current = null;
217
+ // Only reset when the key has actually changed (not on initial mount).
218
+ if (hasMountedRef.current) {
219
+ previousValueRef.current = null;
220
+ newValueRef.current = null;
221
+ sourceValueRef.current = undefined;
222
+ resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }];
223
+ }
224
+ // Force a cache re-read on every (re)subscription so any side effects from
225
+ // subscribeToKey (e.g. addNullishStorageKey for skippable collection member ids)
226
+ // are reflected in the next getSnapshot. Resetting this flag does not change
227
+ // resultRef by itself, so it doesn't cause an extra mount render.
216
228
  shouldGetCachedValueRef.current = true;
217
- sourceValueRef.current = undefined;
218
- resultRef.current = [undefined, { status: (options === null || options === void 0 ? void 0 : options.initWithStoredValues) === false ? 'loaded' : 'loading' }];
229
+ hasMountedRef.current = true;
219
230
  isConnectingRef.current = true;
220
231
  onStoreChangeFnRef.current = onStoreChange;
221
232
  connectionRef.current = OnyxConnectionManager_1.default.connect({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.70",
3
+ "version": "3.0.72",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",