react-native-onyx 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -430,14 +430,14 @@ The action logs use this naming convention:
430
430
 
431
431
  # Development
432
432
 
433
- `react-native` bundles source using the `metro` bundler. `metro` does not follow symlinks, so we can't use `npm link` to
434
- link a local version of Onyx during development
433
+ React Native bundles source code using the `metro` bundler. Until React Native 0.73, `metro` does not follow symlinks, so we can't use `npm link` to
434
+ link a local version of Onyx during development. Fortunately, we have set up a workflow that's easy to follow and enables
435
+ you to edit the Onyx source directly in the Onyx repo, and have those changes hot-reload in a React Native project in realtime.
435
436
 
436
- To quickly test small changes you can directly go to `node_modules/react-native-onyx` in the parent project and tweak original source code.
437
+ 1. In one terminal tab, open the `react-native-onyx` directory and run `npm run build:watch`
438
+ 2. In another terminal tab, open your React Native project and run `npx link publish <path_to_onyx_directory_on_your_machine>`
439
+ 3. Then run your React Native project as normal!
437
440
 
438
- To continuously work on Onyx we have to set up a task that copies content to parent project's `node_modules/react-native-onyx`:
439
- 1. Work on Onyx feature or a fix
440
- 2. Save files
441
- 3. Optional: run `npm run build` (if you're working or want to test on a non react-native project)
442
- - `npm link` would actually work outside of `react-native` and it can be used to link Onyx locally for a web only project
443
- 4. Copy Onyx to consumer project's `node_modules/react-native-onyx`
441
+ Now you can make changes directly to the `react-native-onyx` source code and your React Native project should-hot reload with those changes in realtime.
442
+
443
+ _Note:_ If you want to unlink `react-native-onyx`, simply run `npm install` from your React Native project directory again. That will reinstall `react-native-onyx` from npm.
package/dist/Onyx.js CHANGED
@@ -510,11 +510,12 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
510
510
  * @private
511
511
  * @param {String} key
512
512
  * @param {*} data
513
+ * @param {*} prevData
513
514
  * @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
514
515
  * @param {boolean} [notifyRegularSubscibers=true]
515
516
  * @param {boolean} [notifyWithOnyxSubscibers=true]
516
517
  */
517
- function keyChanged(key, data, canUpdateSubscriber, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
518
+ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
518
519
  // Add or remove this key from the recentlyAccessedKeys lists
519
520
  if (!underscore_1.default.isNull(data)) {
520
521
  addLastAccessedKey(key);
@@ -528,7 +529,7 @@ function keyChanged(key, data, canUpdateSubscriber, notifyRegularSubscibers = tr
528
529
  const stateMappingKeys = underscore_1.default.keys(callbackToStateMapping);
529
530
  for (let i = 0; i < stateMappingKeys.length; i++) {
530
531
  const subscriber = callbackToStateMapping[stateMappingKeys[i]];
531
- if (!subscriber || !isKeyMatch(subscriber.key, key) || (underscore_1.default.isFunction(canUpdateSubscriber) && !canUpdateSubscriber(subscriber))) {
532
+ if (!subscriber || !isKeyMatch(subscriber.key, key) || !canUpdateSubscriber(subscriber)) {
532
533
  continue;
533
534
  }
534
535
  // Subscriber is a regular call to connect() and provided a callback
@@ -556,13 +557,13 @@ function keyChanged(key, data, canUpdateSubscriber, notifyRegularSubscibers = tr
556
557
  // returned by the selector and only when the selected data has changed.
557
558
  if (subscriber.selector) {
558
559
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
559
- const prevData = prevState[subscriber.statePropertyName];
560
- const newData = {
560
+ const prevWithOnyxData = prevState[subscriber.statePropertyName];
561
+ const newWithOnyxData = {
561
562
  [key]: getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state),
562
563
  };
563
- const prevDataWithNewData = Object.assign(Object.assign({}, prevData), newData);
564
- if (!(0, fast_equals_1.deepEqual)(prevData, prevDataWithNewData)) {
565
- PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keyChanged', key);
564
+ const prevDataWithNewData = Object.assign(Object.assign({}, prevWithOnyxData), newWithOnyxData);
565
+ if (!(0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
566
+ PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
566
567
  return {
567
568
  [subscriber.statePropertyName]: prevDataWithNewData,
568
569
  };
@@ -584,8 +585,8 @@ function keyChanged(key, data, canUpdateSubscriber, notifyRegularSubscibers = tr
584
585
  // If the subscriber has a selector, then the component's state must only be updated with the data
585
586
  // returned by the selector and only if the selected data has changed.
586
587
  if (subscriber.selector) {
587
- subscriber.withOnyxInstance.setStateProxy((prevState) => {
588
- const previousValue = getSubsetOfData(prevState[subscriber.statePropertyName], subscriber.selector, subscriber.withOnyxInstance.state);
588
+ subscriber.withOnyxInstance.setStateProxy(() => {
589
+ const previousValue = getSubsetOfData(prevData, subscriber.selector, subscriber.withOnyxInstance.state);
589
590
  const newValue = getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state);
590
591
  if (!(0, fast_equals_1.deepEqual)(previousValue, newValue)) {
591
592
  return {
@@ -598,15 +599,15 @@ function keyChanged(key, data, canUpdateSubscriber, notifyRegularSubscibers = tr
598
599
  }
599
600
  // If we did not match on a collection key then we just set the new data to the state property
600
601
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
601
- const previousData = prevState[subscriber.statePropertyName];
602
+ const prevWithOnyxData = prevState[subscriber.statePropertyName];
602
603
  // Avoids triggering unnecessary re-renders when feeding empty objects
603
- if (utils_1.default.areObjectsEmpty(data, previousData)) {
604
+ if (utils_1.default.areObjectsEmpty(data, prevWithOnyxData)) {
604
605
  return null;
605
606
  }
606
- if (previousData === data) {
607
+ if (prevWithOnyxData === data) {
607
608
  return null;
608
609
  }
609
- PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keyChanged', key);
610
+ PerformanceUtils.logSetStateCall(subscriber, prevData, data, 'keyChanged', key);
610
611
  return {
611
612
  [subscriber.statePropertyName]: data,
612
613
  };
@@ -829,12 +830,13 @@ function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
829
830
  *
830
831
  * @param {String} key
831
832
  * @param {*} value
833
+ * @param {*} prevValue
832
834
  * @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
833
835
  * @returns {Promise}
834
836
  */
835
- function scheduleSubscriberUpdate(key, value, canUpdateSubscriber) {
836
- const promise = Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber, true, false));
837
- batchUpdates(() => keyChanged(key, value, canUpdateSubscriber, false, true));
837
+ function scheduleSubscriberUpdate(key, value, prevValue, canUpdateSubscriber = () => true) {
838
+ const promise = Promise.resolve().then(() => keyChanged(key, value, prevValue, canUpdateSubscriber, true, false));
839
+ batchUpdates(() => keyChanged(key, value, prevValue, canUpdateSubscriber, false, true));
838
840
  return Promise.all([maybeFlushBatchUpdates(), promise]);
839
841
  }
840
842
  /**
@@ -859,8 +861,9 @@ function scheduleNotifyCollectionSubscribers(key, value) {
859
861
  * @return {Promise}
860
862
  */
861
863
  function remove(key) {
864
+ const prevValue = OnyxCache_1.default.getValue(key, false);
862
865
  OnyxCache_1.default.drop(key);
863
- scheduleSubscriberUpdate(key, null);
866
+ scheduleSubscriberUpdate(key, null, prevValue);
864
867
  return storage_1.default.removeItem(key);
865
868
  }
866
869
  /**
@@ -920,6 +923,7 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
920
923
  function broadcastUpdate(key, value, method, hasChanged, wasRemoved = false) {
921
924
  // Logging properties only since values could be sensitive things we don't want to log
922
925
  Logger.logInfo(`${method}() called for key: ${key}${underscore_1.default.isObject(value) ? ` properties: ${underscore_1.default.keys(value).join(',')}` : ''}`);
926
+ const prevValue = OnyxCache_1.default.getValue(key, false);
923
927
  // Update subscribers if the cached value has changed, or when the subscriber specifically requires
924
928
  // all updates regardless of value changes (indicated by initWithStoredValues set to false).
925
929
  if (hasChanged && !wasRemoved) {
@@ -928,7 +932,7 @@ function broadcastUpdate(key, value, method, hasChanged, wasRemoved = false) {
928
932
  else {
929
933
  OnyxCache_1.default.addToAccessedKeys(key);
930
934
  }
931
- return scheduleSubscriberUpdate(key, value, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
935
+ return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
932
936
  }
933
937
  /**
934
938
  * @param {String} key
@@ -1011,9 +1015,10 @@ function prepareKeyValuePairsForStorage(data) {
1011
1015
  function multiSet(data) {
1012
1016
  const keyValuePairs = prepareKeyValuePairsForStorage(data);
1013
1017
  const updatePromises = underscore_1.default.map(keyValuePairs, ([key, value]) => {
1018
+ const prevValue = OnyxCache_1.default.getValue(key, false);
1014
1019
  // Update cache and optimistically inform subscribers on the next tick
1015
1020
  OnyxCache_1.default.set(key, value);
1016
- return scheduleSubscriberUpdate(key, value);
1021
+ return scheduleSubscriberUpdate(key, value, prevValue);
1017
1022
  });
1018
1023
  return storage_1.default.multiSet(keyValuePairs)
1019
1024
  .catch((error) => evictStorageAndRetry(error, multiSet, data))
@@ -1130,10 +1135,10 @@ function merge(key, changes) {
1130
1135
  */
1131
1136
  function initializeWithDefaultKeyStates() {
1132
1137
  return storage_1.default.multiGet(underscore_1.default.keys(defaultKeyStates)).then((pairs) => {
1133
- const asObject = underscore_1.default.object(pairs);
1134
- const merged = utils_1.default.fastMerge(asObject, defaultKeyStates);
1138
+ const existingDataAsObject = underscore_1.default.object(pairs);
1139
+ const merged = utils_1.default.fastMerge(existingDataAsObject, defaultKeyStates);
1135
1140
  OnyxCache_1.default.merge(merged);
1136
- underscore_1.default.each(merged, (val, key) => keyChanged(key, val));
1141
+ underscore_1.default.each(merged, (val, key) => keyChanged(key, val, existingDataAsObject));
1137
1142
  });
1138
1143
  }
1139
1144
  /**
@@ -1201,7 +1206,7 @@ function clear(keysToPreserve = []) {
1201
1206
  const updatePromises = [];
1202
1207
  // Notify the subscribers for each key/value group so they can receive the new values
1203
1208
  underscore_1.default.each(keyValuesToResetIndividually, (value, key) => {
1204
- updatePromises.push(scheduleSubscriberUpdate(key, value));
1209
+ updatePromises.push(scheduleSubscriberUpdate(key, value, OnyxCache_1.default.getValue(key, false)));
1205
1210
  });
1206
1211
  underscore_1.default.each(keyValuesToResetAsCollection, (value, key) => {
1207
1212
  updatePromises.push(scheduleNotifyCollectionSubscribers(key, value));
@@ -1401,8 +1406,9 @@ function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCach
1401
1406
  Promise.all([addAllSafeEvictionKeysToRecentlyAccessedList(), initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);
1402
1407
  if (shouldSyncMultipleInstances && underscore_1.default.isFunction(storage_1.default.keepInstancesSync)) {
1403
1408
  storage_1.default.keepInstancesSync((key, value) => {
1409
+ const prevValue = OnyxCache_1.default.getValue(key, false);
1404
1410
  OnyxCache_1.default.set(key, value);
1405
- keyChanged(key, value);
1411
+ keyChanged(key, value, prevValue);
1406
1412
  });
1407
1413
  }
1408
1414
  }
@@ -39,9 +39,10 @@ declare class OnyxCache {
39
39
  /**
40
40
  * Get a cached value from storage
41
41
  * @param {string} key
42
+ * @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
42
43
  * @returns {*}
43
44
  */
44
- getValue(key: string): any;
45
+ getValue(key: string, shouldReindexCache?: boolean | undefined): any;
45
46
  /**
46
47
  * Check whether cache has data for the given key
47
48
  * @param {string} key
package/dist/OnyxCache.js CHANGED
@@ -52,10 +52,13 @@ class OnyxCache {
52
52
  /**
53
53
  * Get a cached value from storage
54
54
  * @param {string} key
55
+ * @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
55
56
  * @returns {*}
56
57
  */
57
- getValue(key) {
58
- this.addToAccessedKeys(key);
58
+ getValue(key, shouldReindexCache = true) {
59
+ if (shouldReindexCache) {
60
+ this.addToAccessedKeys(key);
61
+ }
59
62
  return this.storageMap[key];
60
63
  }
61
64
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -32,6 +32,7 @@
32
32
  "typecheck": "tsc --noEmit",
33
33
  "test": "jest",
34
34
  "build": "tsc -p tsconfig.build.json && cp ./lib/*.d.ts ./dist",
35
+ "build:watch": "nodemon --watch lib --exec \"npm run build && npm pack\"",
35
36
  "build:docs": "node buildDocs.js",
36
37
  "lint-tests": "eslint tests/**",
37
38
  "prettier": "prettier --write ."
@@ -65,6 +66,7 @@
65
66
  "jest-cli": "^26.5.2",
66
67
  "jsdoc-to-markdown": "^7.1.0",
67
68
  "metro-react-native-babel-preset": "^0.77.0",
69
+ "nodemon": "^3.0.3",
68
70
  "prettier": "^2.8.8",
69
71
  "prop-types": "^15.7.2",
70
72
  "react": "18.2.0",
@@ -81,9 +83,9 @@
81
83
  "idb-keyval": "^6.2.1",
82
84
  "react": ">=18.1.0",
83
85
  "react-dom": ">=18.1.0",
86
+ "react-native-device-info": "^10.3.0",
84
87
  "react-native-performance": "^5.1.0",
85
- "react-native-quick-sqlite": "^8.0.0-beta.2",
86
- "react-native-device-info": "^10.3.0"
88
+ "react-native-quick-sqlite": "^8.0.0-beta.2"
87
89
  },
88
90
  "peerDependenciesMeta": {
89
91
  "idb-keyval": {