react-native-onyx 3.0.29 → 3.0.31

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/API.md CHANGED
@@ -177,7 +177,7 @@ applied in the order they were called. Note: `Onyx.set()` calls do not work this
177
177
  **Example**
178
178
  ```js
179
179
  Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Joe']); // -> ['Joe']
180
- Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Jack']
180
+ Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack']
181
181
  Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1}
182
182
  Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
183
183
  ```
package/dist/Onyx.js CHANGED
@@ -64,7 +64,7 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedK
64
64
  // When a collection is updated, individual members sync separately to other tabs
65
65
  // Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
66
66
  const isKeyCollectionMember = OnyxUtils_1.default.isCollectionMember(key);
67
- OnyxUtils_1.default.keyChanged(key, value, undefined, true, isKeyCollectionMember);
67
+ OnyxUtils_1.default.keyChanged(key, value, undefined, isKeyCollectionMember);
68
68
  });
69
69
  }
70
70
  if (maxCachedKeysCount > 0) {
@@ -56,14 +56,6 @@ declare function initStoreValues(keys: DeepRecord<string, OnyxKey>, initialKeySt
56
56
  */
57
57
  declare function sendActionToDevTools(method: typeof METHOD.MERGE_COLLECTION | typeof METHOD.MULTI_SET | typeof METHOD.SET_COLLECTION, key: undefined, value: OnyxCollection<KeyValueMapping[OnyxKey]>, mergedValue?: undefined): void;
58
58
  declare function sendActionToDevTools(method: Exclude<OnyxMethod, typeof METHOD.MERGE_COLLECTION | typeof METHOD.MULTI_SET | typeof METHOD.SET_COLLECTION>, key: OnyxKey, value: OnyxEntry<KeyValueMapping[OnyxKey]>, mergedValue?: OnyxEntry<KeyValueMapping[OnyxKey]>): void;
59
- /**
60
- * We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
61
- * This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
62
- * update operations. Instead of calling the subscribers for each update operation, we batch them together which will
63
- * cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
64
- */
65
- declare function maybeFlushBatchUpdates(): Promise<void>;
66
- declare function batchUpdates(updates: () => void): Promise<void>;
67
59
  /**
68
60
  * Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
69
61
  * and runs it through a reducer function to return a subset of the data according to a selector.
@@ -148,14 +140,14 @@ declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionK
148
140
  /**
149
141
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
150
142
  */
151
- declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyConnectSubscribers?: boolean): void;
143
+ declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, partialPreviousCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined): void;
152
144
  /**
153
145
  * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
154
146
  *
155
147
  * @example
156
148
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
157
149
  */
158
- declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: CallbackToStateMapping<OnyxKey>) => boolean, notifyConnectSubscribers?: boolean, isProcessingCollectionUpdate?: boolean): void;
150
+ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: CallbackToStateMapping<OnyxKey>) => boolean, isProcessingCollectionUpdate?: boolean): void;
159
151
  /**
160
152
  * Sends the data obtained from the keys to the connection.
161
153
  */
@@ -177,7 +169,7 @@ declare function getCollectionDataAndSendAsObject<TKey extends OnyxKey>(matching
177
169
  */
178
170
  declare function scheduleSubscriberUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: CallbackToStateMapping<OnyxKey>) => boolean, isProcessingCollectionUpdate?: boolean): Promise<void>;
179
171
  /**
180
- * This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
172
+ * This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
181
173
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
182
174
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
183
175
  */
@@ -327,8 +319,6 @@ declare const OnyxUtils: {
327
319
  getDeferredInitTask: typeof getDeferredInitTask;
328
320
  initStoreValues: typeof initStoreValues;
329
321
  sendActionToDevTools: typeof sendActionToDevTools;
330
- maybeFlushBatchUpdates: typeof maybeFlushBatchUpdates;
331
- batchUpdates: typeof batchUpdates;
332
322
  get: typeof get;
333
323
  getAllKeys: typeof getAllKeys;
334
324
  getCollectionKeys: typeof getCollectionKeys;
package/dist/OnyxUtils.js CHANGED
@@ -44,7 +44,6 @@ const DevTools_1 = __importDefault(require("./DevTools"));
44
44
  const Logger = __importStar(require("./Logger"));
45
45
  const OnyxCache_1 = __importStar(require("./OnyxCache"));
46
46
  const Str = __importStar(require("./Str"));
47
- const batch_1 = __importDefault(require("./batch"));
48
47
  const storage_1 = __importDefault(require("./storage"));
49
48
  const utils_1 = __importDefault(require("./utils"));
50
49
  const createDeferredTask_1 = __importDefault(require("./createDeferredTask"));
@@ -76,6 +75,8 @@ const MAX_STORAGE_OPERATION_RETRY_ATTEMPTS = 5;
76
75
  // Key/value store of Onyx key and arrays of values to merge
77
76
  let mergeQueue = {};
78
77
  let mergeQueuePromise = {};
78
+ // Used to schedule subscriber update to the macro task queue
79
+ let nextMacrotaskPromise = null;
79
80
  // Holds a mapping of all the React components that want their state subscribed to a store key
80
81
  let callbackToStateMapping = {};
81
82
  // Keeps a copy of the values of the onyx collection keys as a map for faster lookups
@@ -84,8 +85,6 @@ let onyxCollectionKeySet = new Set();
84
85
  let onyxKeyToSubscriptionIDs = new Map();
85
86
  // Optional user-provided key value states set when Onyx initializes or clears
86
87
  let defaultKeyStates = {};
87
- let batchUpdatesPromise = null;
88
- let batchUpdatesQueue = [];
89
88
  // Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
90
89
  let lastConnectionCallbackData = new Map();
91
90
  let snapshotKey = null;
@@ -164,39 +163,6 @@ function initStoreValues(keys, initialKeyStates, evictableKeys) {
164
163
  function sendActionToDevTools(method, key, value, mergedValue = undefined) {
165
164
  DevTools_1.default.registerAction(utils_1.default.formatActionName(method, key), value, key ? { [key]: mergedValue || value } : value);
166
165
  }
167
- /**
168
- * We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
169
- * This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
170
- * update operations. Instead of calling the subscribers for each update operation, we batch them together which will
171
- * cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
172
- */
173
- function maybeFlushBatchUpdates() {
174
- if (batchUpdatesPromise) {
175
- return batchUpdatesPromise;
176
- }
177
- batchUpdatesPromise = new Promise((resolve) => {
178
- /* We use (setTimeout, 0) here which should be called once native module calls are flushed (usually at the end of the frame)
179
- * We may investigate if (setTimeout, 1) (which in React Native is equal to requestAnimationFrame) works even better
180
- * then the batch will be flushed on next frame.
181
- */
182
- setTimeout(() => {
183
- const updatesCopy = batchUpdatesQueue;
184
- batchUpdatesQueue = [];
185
- batchUpdatesPromise = null;
186
- (0, batch_1.default)(() => {
187
- for (const applyUpdates of updatesCopy) {
188
- applyUpdates();
189
- }
190
- });
191
- resolve();
192
- }, 0);
193
- });
194
- return batchUpdatesPromise;
195
- }
196
- function batchUpdates(updates) {
197
- batchUpdatesQueue.push(updates);
198
- return maybeFlushBatchUpdates();
199
- }
200
166
  /**
201
167
  * Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
202
168
  * and runs it through a reducer function to return a subset of the data according to a selector.
@@ -522,7 +488,7 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
522
488
  /**
523
489
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
524
490
  */
525
- function keysChanged(collectionKey, partialCollection, partialPreviousCollection, notifyConnectSubscribers = true) {
491
+ function keysChanged(collectionKey, partialCollection, partialPreviousCollection) {
526
492
  // We prepare the "cached collection" which is the entire collection + the new partial data that
527
493
  // was merged in via mergeCollection().
528
494
  const cachedCollection = getCachedCollection(collectionKey);
@@ -550,9 +516,6 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
550
516
  const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
551
517
  // Regular Onyx.connect() subscriber found.
552
518
  if (typeof subscriber.callback === 'function') {
553
- if (!notifyConnectSubscribers) {
554
- continue;
555
- }
556
519
  // If they are subscribed to the collection key and using waitForCollectionCallback then we'll
557
520
  // send the whole cached collection.
558
521
  if (isSubscribedToCollectionKey) {
@@ -592,7 +555,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
592
555
  * @example
593
556
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
594
557
  */
595
- function keyChanged(key, value, canUpdateSubscriber = () => true, notifyConnectSubscribers = true, isProcessingCollectionUpdate = false) {
558
+ function keyChanged(key, value, canUpdateSubscriber = () => true, isProcessingCollectionUpdate = false) {
596
559
  var _a, _b;
597
560
  // Add or remove this key from the recentlyAccessedKeys lists
598
561
  if (value !== null) {
@@ -630,9 +593,6 @@ function keyChanged(key, value, canUpdateSubscriber = () => true, notifyConnectS
630
593
  }
631
594
  // Subscriber is a regular call to connect() and provided a callback
632
595
  if (typeof subscriber.callback === 'function') {
633
- if (!notifyConnectSubscribers) {
634
- continue;
635
- }
636
596
  if (lastConnectionCallbackData.has(subscriber.subscriptionID) && lastConnectionCallbackData.get(subscriber.subscriptionID) === value) {
637
597
  continue;
638
598
  }
@@ -701,6 +661,22 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
701
661
  sendDataToConnection(mapping, data, mapping.key);
702
662
  });
703
663
  }
664
+ /**
665
+ * Delays promise resolution until the next macrotask to prevent race condition if the key subscription is in progress.
666
+ *
667
+ * @param callback The keyChanged/keysChanged callback
668
+ * */
669
+ function prepareSubscriberUpdate(callback) {
670
+ if (!nextMacrotaskPromise) {
671
+ nextMacrotaskPromise = new Promise((resolve) => {
672
+ setTimeout(() => {
673
+ nextMacrotaskPromise = null;
674
+ resolve();
675
+ }, 0);
676
+ });
677
+ }
678
+ return Promise.all([nextMacrotaskPromise, Promise.resolve().then(callback)]).then();
679
+ }
704
680
  /**
705
681
  * Schedules an update that will be appended to the macro task queue (so it doesn't update the subscribers immediately).
706
682
  *
@@ -708,19 +684,15 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
708
684
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
709
685
  */
710
686
  function scheduleSubscriberUpdate(key, value, canUpdateSubscriber = () => true, isProcessingCollectionUpdate = false) {
711
- const promise = Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber, true, isProcessingCollectionUpdate));
712
- batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false, isProcessingCollectionUpdate));
713
- return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
687
+ return prepareSubscriberUpdate(() => keyChanged(key, value, canUpdateSubscriber, isProcessingCollectionUpdate));
714
688
  }
715
689
  /**
716
- * This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
690
+ * This method is similar to scheduleSubscriberUpdate but it is built for working specifically with collections
717
691
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
718
692
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
719
693
  */
720
694
  function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
721
- const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true));
722
- batchUpdates(() => keysChanged(key, value, previousValue, false));
723
- return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
695
+ return prepareSubscriberUpdate(() => keysChanged(key, value, previousValue));
724
696
  }
725
697
  /**
726
698
  * Remove a key from Onyx and update the subscribers
@@ -1394,7 +1366,6 @@ function clearOnyxUtilsInternals() {
1394
1366
  mergeQueuePromise = {};
1395
1367
  callbackToStateMapping = {};
1396
1368
  onyxKeyToSubscriptionIDs = new Map();
1397
- batchUpdatesQueue = [];
1398
1369
  lastConnectionCallbackData = new Map();
1399
1370
  }
1400
1371
  const OnyxUtils = {
@@ -1405,8 +1376,6 @@ const OnyxUtils = {
1405
1376
  getDeferredInitTask,
1406
1377
  initStoreValues,
1407
1378
  sendActionToDevTools,
1408
- maybeFlushBatchUpdates,
1409
- batchUpdates,
1410
1379
  get,
1411
1380
  getAllKeys,
1412
1381
  getCollectionKeys,
@@ -1462,10 +1431,6 @@ GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) =>
1462
1431
  // We are reassigning the functions directly so that internal function calls are also decorated
1463
1432
  // @ts-expect-error Reassign
1464
1433
  initStoreValues = (0, metrics_1.default)(initStoreValues, 'OnyxUtils.initStoreValues');
1465
- // @ts-expect-error Reassign
1466
- maybeFlushBatchUpdates = (0, metrics_1.default)(maybeFlushBatchUpdates, 'OnyxUtils.maybeFlushBatchUpdates');
1467
- // @ts-expect-error Reassign
1468
- batchUpdates = (0, metrics_1.default)(batchUpdates, 'OnyxUtils.batchUpdates');
1469
1434
  // @ts-expect-error Complex type signature
1470
1435
  get = (0, metrics_1.default)(get, 'OnyxUtils.get');
1471
1436
  // @ts-expect-error Reassign
package/dist/useOnyx.js CHANGED
@@ -186,10 +186,10 @@ function useOnyx(key, options, dependencies = []) {
186
186
  OnyxSnapshotCache_1.default.setCachedResult(key, cacheKey, result);
187
187
  return result;
188
188
  }
189
- // We get the value from cache while the first connection to Onyx is being made,
190
- // so we can return any cached value right away. After the connection is made, we only
189
+ // We get the value from cache while the first connection to Onyx is being made or if the key has changed,
190
+ // so we can return any cached value right away. For the case where the key has changed, If we don't return the cached value right away, then the UI will show the incorrect (previous) value for a brief period which looks like a UI glitch to the user. After the connection is made, we only
191
191
  // update `newValueRef` when `Onyx.connect()` callback is fired.
192
- if (isFirstConnectionRef.current || shouldGetCachedValueRef.current) {
192
+ if (isFirstConnectionRef.current || shouldGetCachedValueRef.current || key !== previousKey) {
193
193
  // Gets the value from cache and maps it with selector. It changes `null` to `undefined` for `useOnyx` compatibility.
194
194
  const value = OnyxUtils_1.default.tryGetCachedValue(key);
195
195
  const selectedValue = memoizedSelector ? memoizedSelector(value) : value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.29",
3
+ "version": "3.0.31",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -65,7 +65,6 @@
65
65
  "@types/lodash": "^4.14.202",
66
66
  "@types/node": "^20.11.5",
67
67
  "@types/react": "^18.2.14",
68
- "@types/react-dom": "^18.2.18",
69
68
  "@types/react-native": "^0.70.0",
70
69
  "@types/underscore": "^1.11.15",
71
70
  "@typescript-eslint/eslint-plugin": "^8.51.0",
@@ -89,7 +88,6 @@
89
88
  "prettier": "^2.8.8",
90
89
  "prop-types": "^15.7.2",
91
90
  "react": "18.2.0",
92
- "react-dom": "18.2.0",
93
91
  "react-native": "0.76.3",
94
92
  "react-native-device-info": "^10.3.0",
95
93
  "react-native-nitro-modules": "^0.27.2",
@@ -104,7 +102,6 @@
104
102
  "peerDependencies": {
105
103
  "idb-keyval": "^6.2.1",
106
104
  "react": ">=18.1.0",
107
- "react-dom": ">=18.1.0",
108
105
  "react-native": ">=0.75.0",
109
106
  "react-native-device-info": "^10.3.0",
110
107
  "react-native-nitro-modules": ">=0.27.2",
package/dist/batch.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import { unstable_batchedUpdates } from 'react-dom';
2
- export default unstable_batchedUpdates;
package/dist/batch.js DELETED
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const react_dom_1 = require("react-dom");
4
- exports.default = react_dom_1.unstable_batchedUpdates;
@@ -1,2 +0,0 @@
1
- import { unstable_batchedUpdates } from 'react-native';
2
- export default unstable_batchedUpdates;
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const react_native_1 = require("react-native");
4
- exports.default = react_native_1.unstable_batchedUpdates;