react-native-onyx 1.0.121 → 1.0.123

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
@@ -1021,17 +1021,18 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
1021
1021
  *
1022
1022
  * @param {String} key
1023
1023
  * @param {*} value
1024
- * @param {Boolean} hasChanged
1025
1024
  * @param {String} method
1025
+ * @param {Boolean} hasChanged
1026
+ * @param {Boolean} wasRemoved
1026
1027
  * @returns {Promise}
1027
1028
  */
1028
- function broadcastUpdate(key, value, hasChanged, method) {
1029
+ function broadcastUpdate(key, value, method, hasChanged, wasRemoved = false) {
1029
1030
  // Logging properties only since values could be sensitive things we don't want to log
1030
1031
  Logger.logInfo(`${method}() called for key: ${key}${_.isObject(value) ? ` properties: ${_.keys(value).join(',')}` : ''}`);
1031
1032
 
1032
1033
  // Update subscribers if the cached value has changed, or when the subscriber specifically requires
1033
1034
  // all updates regardless of value changes (indicated by initWithStoredValues set to false).
1034
- if (hasChanged) {
1035
+ if (hasChanged && !wasRemoved) {
1035
1036
  cache.set(key, value);
1036
1037
  } else {
1037
1038
  cache.addToAccessedKeys(key);
@@ -1053,18 +1054,18 @@ function hasPendingMergeForKey(key) {
1053
1054
  * Otherwise removes all nested null values in objects and returns the object
1054
1055
  * @param {String} key
1055
1056
  * @param {Mixed} value
1056
- * @returns {Mixed} `null` if the key got removed completely, otherwise the value without null values
1057
+ * @returns {Mixed} The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
1057
1058
  */
1058
1059
  function removeNullValues(key, value) {
1059
1060
  if (_.isNull(value)) {
1060
1061
  remove(key);
1061
- return null;
1062
+ return {value, wasRemoved: true};
1062
1063
  }
1063
1064
 
1064
1065
  // We can remove all null values in an object by merging it with itself
1065
1066
  // utils.fastMerge recursively goes through the object and removes all null values
1066
1067
  // Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
1067
- return utils.removeNestedNullValues(value);
1068
+ return {value: utils.removeNestedNullValues(value), wasRemoved: false};
1068
1069
  }
1069
1070
 
1070
1071
  /**
@@ -1085,41 +1086,48 @@ function set(key, value) {
1085
1086
  return Promise.resolve();
1086
1087
  }
1087
1088
 
1088
- const valueWithoutNull = removeNullValues(key, value);
1089
-
1090
- if (valueWithoutNull === null) {
1091
- return Promise.resolve();
1092
- }
1089
+ // If the value is null, we remove the key from storage
1090
+ const {value: valueAfterRemoving, wasRemoved} = removeNullValues(key, value);
1093
1091
 
1094
1092
  if (hasPendingMergeForKey(key)) {
1095
- Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
1093
+ delete mergeQueue[key]
1096
1094
  }
1097
1095
 
1098
- const hasChanged = cache.hasValueChanged(key, valueWithoutNull);
1096
+ const hasChanged = cache.hasValueChanged(key, valueAfterRemoving);
1099
1097
 
1100
1098
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
1101
- const updatePromise = broadcastUpdate(key, valueWithoutNull, hasChanged, 'set');
1099
+ const updatePromise = broadcastUpdate(key, valueAfterRemoving, 'set', hasChanged, wasRemoved);
1102
1100
 
1103
- // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1104
- if (!hasChanged) {
1101
+ // If the value has not changed or the key got removed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1102
+ if (!hasChanged || wasRemoved) {
1105
1103
  return updatePromise;
1106
1104
  }
1107
1105
 
1108
- return Storage.setItem(key, valueWithoutNull)
1109
- .catch((error) => evictStorageAndRetry(error, set, key, valueWithoutNull))
1106
+ return Storage.setItem(key, valueAfterRemoving)
1107
+ .catch((error) => evictStorageAndRetry(error, set, key, valueAfterRemoving))
1110
1108
  .then(() => updatePromise);
1111
1109
  }
1112
1110
 
1113
1111
  /**
1114
1112
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
1115
1113
  * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
1116
- * to an array of key-value pairs in the above format
1114
+ * to an array of key-value pairs in the above format and removes key-value pairs that are being set to null
1117
1115
  * @private
1118
1116
  * @param {Record} data
1119
1117
  * @return {Array} an array of key - value pairs <[key, value]>
1120
1118
  */
1121
1119
  function prepareKeyValuePairsForStorage(data) {
1122
- return _.map(data, (value, key) => [key, value]);
1120
+ const keyValuePairs = [];
1121
+
1122
+ _.forEach(data, (value, key) => {
1123
+ const {value: valueAfterRemoving, wasRemoved} = removeNullValues(key, value);
1124
+
1125
+ if (wasRemoved) return
1126
+
1127
+ keyValuePairs.push([key, valueAfterRemoving])
1128
+ });
1129
+
1130
+ return keyValuePairs
1123
1131
  }
1124
1132
 
1125
1133
  /**
@@ -1142,25 +1150,13 @@ function multiSet(data) {
1142
1150
 
1143
1151
  const keyValuePairs = prepareKeyValuePairsForStorage(data);
1144
1152
 
1145
- const updatePromises = _.map(data, (val, key) => {
1153
+ const updatePromises = _.map(keyValuePairs, ([key, value]) => {
1146
1154
  // Update cache and optimistically inform subscribers on the next tick
1147
- cache.set(key, val);
1148
- return scheduleSubscriberUpdate(key, val);
1155
+ cache.set(key, value);
1156
+ return scheduleSubscriberUpdate(key, value);
1149
1157
  });
1150
1158
 
1151
- const keyValuePairsWithoutNull = _.filter(
1152
- _.map(keyValuePairs, ([key, value]) => {
1153
- const valueWithoutNull = removeNullValues(key, value);
1154
-
1155
- if (valueWithoutNull === null) {
1156
- return;
1157
- }
1158
- return [key, valueWithoutNull];
1159
- }),
1160
- Boolean,
1161
- );
1162
-
1163
- return Storage.multiSet(keyValuePairsWithoutNull)
1159
+ return Storage.multiSet(keyValuePairs)
1164
1160
  .catch((error) => evictStorageAndRetry(error, multiSet, data))
1165
1161
  .then(() => Promise.all(updatePromises));
1166
1162
  }
@@ -1236,6 +1232,9 @@ function merge(key, changes) {
1236
1232
  mergeQueue[key] = [changes];
1237
1233
 
1238
1234
  mergeQueuePromise[key] = get(key).then((existingValue) => {
1235
+ // Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
1236
+ if (mergeQueue[key] == null) return
1237
+
1239
1238
  try {
1240
1239
  // 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)
1241
1240
  // We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
@@ -1250,10 +1249,7 @@ function merge(key, changes) {
1250
1249
  delete mergeQueuePromise[key];
1251
1250
 
1252
1251
  // If the batched changes equal null, we want to remove the key from storage, to reduce storage size
1253
- if (_.isNull(batchedChanges)) {
1254
- remove(key);
1255
- return;
1256
- }
1252
+ const {wasRemoved} = removeNullValues(key, batchedChanges)
1257
1253
 
1258
1254
  // After that we merge the batched changes with the existing value
1259
1255
  // We can remove null values from the "modifiedData", because "null" implicates that the user wants to remove a value from storage.
@@ -1271,10 +1267,10 @@ function merge(key, changes) {
1271
1267
  const hasChanged = cache.hasValueChanged(key, modifiedData);
1272
1268
 
1273
1269
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
1274
- const updatePromise = broadcastUpdate(key, modifiedData, hasChanged, 'merge');
1270
+ const updatePromise = broadcastUpdate(key, modifiedData, 'merge', hasChanged, wasRemoved);
1275
1271
 
1276
1272
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1277
- if (!hasChanged || isClearing) {
1273
+ if (!hasChanged || isClearing || wasRemoved) {
1278
1274
  return updatePromise;
1279
1275
  }
1280
1276
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.121",
3
+ "version": "1.0.123",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "react-native": "native.js",
32
32
  "main": "native.js",
33
- "browser": "web.js",
33
+ "browser": "lib/index.js",
34
34
  "types": "lib/index.d.ts",
35
35
  "scripts": {
36
36
  "lint": "eslint .",
@@ -40,7 +40,6 @@
40
40
  "build:docs": "node buildDocs.js",
41
41
  "e2e": "playwright test",
42
42
  "e2e-ui": "playwright test --ui",
43
- "postinstall": "cd tests/e2e/app && npm install",
44
43
  "prettier": "prettier --write ."
45
44
  },
46
45
  "dependencies": {