react-native-onyx 1.0.54 → 1.0.55

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/lib/Onyx.js CHANGED
@@ -848,19 +848,33 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
848
848
  *
849
849
  * @param {String} key
850
850
  * @param {*} value
851
+ * @param {Boolean} hasChanged
851
852
  * @param {String} method
852
853
  */
853
- function broadcastUpdate(key, value, method) {
854
+ function broadcastUpdate(key, value, hasChanged, method) {
854
855
  // Logging properties only since values could be sensitive things we don't want to log
855
856
  Logger.logInfo(`${method}() called for key: ${key}${_.isObject(value) ? ` properties: ${_.keys(value).join(',')}` : ''}`);
856
857
 
857
858
  // Update subscribers if the cached value has changed, or when the subscriber specifically requires
858
859
  // all updates regardless of value changes (indicated by initWithStoredValues set to false).
859
- const hasChanged = cache.hasValueChanged(key, value);
860
- cache.set(key, value);
860
+ if (hasChanged) {
861
+ cache.set(key, value);
862
+ } else {
863
+ cache.addToAccessedKeys(key);
864
+ }
865
+
861
866
  notifySubscribersOnNextTick(key, value, subscriber => hasChanged || subscriber.initWithStoredValues === false);
862
867
  }
863
868
 
869
+ /**
870
+ * @private
871
+ * @param {String} key
872
+ * @returns {Boolean}
873
+ */
874
+ function hasPendingMergeForKey(key) {
875
+ return Boolean(mergeQueue[key]);
876
+ }
877
+
864
878
  /**
865
879
  * Write a value to our store with the given key
866
880
  *
@@ -874,8 +888,19 @@ function set(key, value) {
874
888
  return remove(key);
875
889
  }
876
890
 
891
+ if (hasPendingMergeForKey(key)) {
892
+ Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
893
+ }
894
+
895
+ const hasChanged = cache.hasValueChanged(key, value);
896
+
877
897
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
878
- broadcastUpdate(key, value, 'set');
898
+ broadcastUpdate(key, value, hasChanged, 'set');
899
+
900
+ // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
901
+ if (!hasChanged) {
902
+ return Promise.resolve();
903
+ }
879
904
 
880
905
  return Storage.setItem(key, value)
881
906
  .catch(error => evictStorageAndRetry(error, set, key, value));
@@ -918,11 +943,11 @@ function multiSet(data) {
918
943
  * Merges an array of changes with an existing value
919
944
  *
920
945
  * @private
921
- * @param {Array<*>} changes Array of changes that should be applied to the existing value
922
946
  * @param {*} existingValue
947
+ * @param {Array<*>} changes Array of changes that should be applied to the existing value
923
948
  * @returns {*}
924
949
  */
925
- function applyMerge(changes, existingValue) {
950
+ function applyMerge(existingValue, changes) {
926
951
  const lastChange = _.last(changes);
927
952
 
928
953
  if (_.isArray(existingValue) || _.isArray(lastChange)) {
@@ -931,14 +956,10 @@ function applyMerge(changes, existingValue) {
931
956
 
932
957
  if (_.isObject(existingValue) || _.every(changes, _.isObject)) {
933
958
  // Object values are merged one after the other
934
- return _.reduce(changes, (modifiedData, change) => {
935
- // lodash adds a small overhead so we don't use it here
936
- // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
937
- const newData = Object.assign({}, fastMerge(modifiedData, change));
938
-
939
- // Remove all first level keys that are explicitly set to null.
940
- return _.omit(newData, value => _.isNull(value));
941
- }, existingValue || {});
959
+ // lodash adds a small overhead so we don't use it here
960
+ // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
961
+ return _.reduce(changes, (modifiedData, change) => fastMerge(modifiedData, change),
962
+ existingValue || {});
942
963
  }
943
964
 
944
965
  // If we have anything else we can't merge it so we'll
@@ -979,17 +1000,30 @@ function merge(key, changes) {
979
1000
  .then((existingValue) => {
980
1001
  try {
981
1002
  // We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
982
- const batchedChanges = applyMerge(mergeQueue[key]);
1003
+ const batchedChanges = applyMerge(undefined, mergeQueue[key]);
983
1004
 
984
1005
  // Clean up the write queue so we
985
1006
  // don't apply these changes again
986
1007
  delete mergeQueue[key];
987
1008
 
988
1009
  // After that we merge the batched changes with the existing value
989
- const modifiedData = applyMerge([batchedChanges], existingValue);
1010
+ let modifiedData = applyMerge(existingValue, [batchedChanges]);
1011
+
1012
+ // For objects, the key for null values needs to be removed from the object to ensure the value will get removed from storage completely.
1013
+ // On native, SQLite will remove top-level keys that are null. To be consistent, we remove them on web too.
1014
+ if (!_.isArray(modifiedData) && _.isObject(modifiedData)) {
1015
+ modifiedData = _.omit(modifiedData, value => _.isNull(value));
1016
+ }
1017
+
1018
+ const hasChanged = cache.hasValueChanged(key, modifiedData);
990
1019
 
991
1020
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
992
- broadcastUpdate(key, modifiedData, 'merge');
1021
+ broadcastUpdate(key, modifiedData, hasChanged, 'merge');
1022
+
1023
+ // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1024
+ if (!hasChanged) {
1025
+ return Promise.resolve();
1026
+ }
993
1027
 
994
1028
  return Storage.mergeItem(key, batchedChanges, modifiedData);
995
1029
  } catch (error) {
@@ -1000,15 +1034,6 @@ function merge(key, changes) {
1000
1034
  });
1001
1035
  }
1002
1036
 
1003
- /**
1004
- * @private
1005
- * @param {String} key
1006
- * @returns {Boolean}
1007
- */
1008
- function hasPendingMergeForKey(key) {
1009
- return Boolean(mergeQueue[key]);
1010
- }
1011
-
1012
1037
  /**
1013
1038
  * Merge user provided default key value pairs.
1014
1039
  * @private
@@ -4,9 +4,8 @@ import fastMerge from '../../fastMerge';
4
4
  let storageMapInternal = {};
5
5
 
6
6
  const set = jest.fn((key, value) => {
7
- // eslint-disable-next-line no-param-reassign
8
7
  storageMapInternal[key] = value;
9
- return Promise.resolve(storageMapInternal[key]);
8
+ return Promise.resolve(value);
10
9
  });
11
10
 
12
11
  const localForageMock = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",