react-native-onyx 1.0.96 → 1.0.98

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.d.ts CHANGED
@@ -243,7 +243,10 @@ declare function clear(keysToPreserve?: OnyxKey[]): Promise<void>;
243
243
  * @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
244
244
  * @param collection Object collection keyed by individual collection member keys and values
245
245
  */
246
- declare function mergeCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: Collection<TKey, TMap, NullishDeep<KeyValueMapping[TKey]>>): Promise<void>;
246
+ declare function mergeCollection<TKey extends CollectionKeyBase, TMap>(
247
+ collectionKey: TKey,
248
+ collection: Collection<TKey, TMap, NullishDeep<KeyValueMapping[TKey]>>,
249
+ ): Promise<void>;
247
250
 
248
251
  /**
249
252
  * Insert API responses and lifecycle data into Onyx
package/lib/Onyx.js CHANGED
@@ -1041,13 +1041,7 @@ function set(key, value) {
1041
1041
  Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
1042
1042
  }
1043
1043
 
1044
- // We can remove all null values in an object by merging it with itself
1045
- // utils.fastMerge recursively goes through the object and removes all null values
1046
- // Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
1047
- let valueWithNullRemoved = value;
1048
- if (typeof value === 'object' && !_.isArray(value)) {
1049
- valueWithNullRemoved = utils.fastMerge(value, value);
1050
- }
1044
+ const valueWithNullRemoved = utils.removeNullObjectValues(value);
1051
1045
 
1052
1046
  const hasChanged = cache.hasValueChanged(key, valueWithNullRemoved);
1053
1047
 
@@ -1104,10 +1098,9 @@ function multiSet(data) {
1104
1098
  * @private
1105
1099
  * @param {*} existingValue
1106
1100
  * @param {Array<*>} changes Array of changes that should be applied to the existing value
1107
- * @param {Boolean} shouldRemoveNullObjectValues
1108
1101
  * @returns {*}
1109
1102
  */
1110
- function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
1103
+ function applyMerge(existingValue, changes) {
1111
1104
  const lastChange = _.last(changes);
1112
1105
 
1113
1106
  if (_.isArray(lastChange)) {
@@ -1116,7 +1109,7 @@ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
1116
1109
 
1117
1110
  if (_.some(changes, _.isObject)) {
1118
1111
  // Object values are then merged one after the other
1119
- return _.reduce(changes, (modifiedData, change) => utils.fastMerge(modifiedData, change, shouldRemoveNullObjectValues),
1112
+ return _.reduce(changes, (modifiedData, change) => utils.fastMerge(modifiedData, change),
1120
1113
  existingValue || {});
1121
1114
  }
1122
1115
 
@@ -1164,8 +1157,7 @@ function merge(key, changes) {
1164
1157
  .then((existingValue) => {
1165
1158
  try {
1166
1159
  // 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)
1167
- // We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
1168
- let batchedChanges = applyMerge(undefined, mergeQueue[key], false);
1160
+ let batchedChanges = applyMerge(undefined, mergeQueue[key]);
1169
1161
 
1170
1162
  if (_.isNull(batchedChanges)) {
1171
1163
  return remove(key);
@@ -1180,16 +1172,15 @@ function merge(key, changes) {
1180
1172
  delete mergeQueuePromise[key];
1181
1173
 
1182
1174
  // After that we merge the batched changes with the existing value
1183
- // We can remove null values from the "modifiedData", because "null" implicates that the user wants to remove a value from storage.
1184
- // The "modifiedData" will be directly "set" in storage instead of being merged
1185
- const modifiedData = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges], true);
1175
+ const updatedValue = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges]);
1176
+ const modifiedData = utils.removeNullObjectValues(updatedValue);
1186
1177
 
1187
1178
  // On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
1188
1179
  // JSON_PATCH generally removes top-level nullish values from the stored object.
1189
- // When there is no existing value though, SQLite will just insert the changes as a new value and thus the null values won't be removed.
1190
- // Therefore we need to remove null values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1180
+ // When there is no existing value though, SQLite will just insert the changes as a new value and thus the top-level nullish values won't be removed.
1181
+ // Therefore we need to remove nullish values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1191
1182
  if (!existingValue) {
1192
- batchedChanges = applyMerge(undefined, mergeQueue[key], true);
1183
+ batchedChanges = utils.removeNullObjectValues(batchedChanges);
1193
1184
  }
1194
1185
 
1195
1186
  const hasChanged = cache.hasValueChanged(key, modifiedData);
@@ -56,7 +56,7 @@ const provider = {
56
56
  const upsertMany = _.map(pairs, ([key, value], index) => {
57
57
  const prev = values[index];
58
58
  const newValue = _.isObject(prev) ? utils.fastMerge(prev, value) : value;
59
- return promisifyRequest(store.put(newValue, key));
59
+ return promisifyRequest(store.put(utils.removeNullObjectValues(newValue), key));
60
60
  });
61
61
  return Promise.all(upsertMany);
62
62
  });
package/lib/types.d.ts CHANGED
@@ -221,4 +221,16 @@ type NullishObjectDeep<ObjectType extends object> = {
221
221
  [KeyType in keyof ObjectType]?: NullishDeep<ObjectType[KeyType]> | null;
222
222
  };
223
223
 
224
- export {CollectionKey, CollectionKeyBase, CustomTypeOptions, DeepRecord, Key, KeyValueMapping, OnyxCollection, OnyxEntry, OnyxKey, Selector, NullishDeep};
224
+ export {
225
+ CollectionKey,
226
+ CollectionKeyBase,
227
+ CustomTypeOptions,
228
+ DeepRecord,
229
+ Key,
230
+ KeyValueMapping,
231
+ OnyxCollection,
232
+ OnyxEntry,
233
+ OnyxKey,
234
+ Selector,
235
+ NullishDeep,
236
+ };
package/lib/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import _ from 'underscore';
1
+ import * as _ from 'underscore';
2
2
 
3
3
  function areObjectsEmpty(a, b) {
4
4
  return (
@@ -25,12 +25,9 @@ function isMergeableObject(val) {
25
25
  /**
26
26
  * @param {Object} target
27
27
  * @param {Object} source
28
- * @param {Boolean} shouldRemoveNullObjectValues
29
28
  * @returns {Object}
30
29
  */
31
- function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
32
- const targetAndSourceIdentical = target === source;
33
-
30
+ function mergeObject(target, source) {
34
31
  const destination = {};
35
32
  if (isMergeableObject(target)) {
36
33
  // lodash adds a small overhead so we don't use it here
@@ -38,13 +35,6 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
38
35
  const targetKeys = Object.keys(target);
39
36
  for (let i = 0; i < targetKeys.length; ++i) {
40
37
  const key = targetKeys[i];
41
-
42
- // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
43
- if (shouldRemoveNullObjectValues && (target[key] === null || source[key] === null)) {
44
- // eslint-disable-next-line no-continue
45
- continue;
46
- }
47
-
48
38
  destination[key] = target[key];
49
39
  }
50
40
  }
@@ -54,22 +44,15 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
54
44
  const sourceKeys = Object.keys(source);
55
45
  for (let i = 0; i < sourceKeys.length; ++i) {
56
46
  const key = sourceKeys[i];
57
-
58
- // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
59
- if (shouldRemoveNullObjectValues && source[key] === null) {
47
+ if (source[key] === undefined) {
60
48
  // eslint-disable-next-line no-continue
61
49
  continue;
62
50
  }
63
-
64
51
  if (!isMergeableObject(source[key]) || !target[key]) {
65
- if (targetAndSourceIdentical) {
66
- // eslint-disable-next-line no-continue
67
- continue;
68
- }
69
52
  destination[key] = source[key];
70
53
  } else {
71
54
  // eslint-disable-next-line no-use-before-define
72
- destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues);
55
+ destination[key] = fastMerge(target[key], source[key]);
73
56
  }
74
57
  }
75
58
 
@@ -77,26 +60,39 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
77
60
  }
78
61
 
79
62
  /**
80
- * Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
81
- *
82
- * We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
83
- * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
84
- * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
85
- *
86
63
  * @param {Object|Array} target
87
64
  * @param {Object|Array} source
88
- * @param {Boolean} shouldRemoveNullObjectValues
89
65
  * @returns {Object|Array}
90
66
  */
91
- function fastMerge(target, source, shouldRemoveNullObjectValues = true) {
67
+ function fastMerge(target, source) {
92
68
  // We have to ignore arrays and nullish values here,
93
69
  // otherwise "mergeObject" will throw an error,
94
70
  // because it expects an object as "source"
95
- if (_.isArray(source) || source === null || source === undefined) {
71
+ if (_.isArray(source) || _.isNull(source) || _.isUndefined(source)) {
96
72
  return source;
97
73
  }
98
- return mergeObject(target, source, shouldRemoveNullObjectValues);
74
+ return mergeObject(target, source);
75
+ }
76
+
77
+ /**
78
+ * We generally want to remove top-level nullish values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
79
+ * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
80
+ * To be consistent with the behaviour for merge, we'll also want to remove nullish values for "set" operations.
81
+ * On web, IndexedDB will keep the top-level keys along with a null value and this uses up storage and memory.
82
+ * This method will ensure that keys for null values are removed before an object is written to disk and cache so that all platforms are storing the data in the same efficient way.
83
+ * @private
84
+ * @param {*} value
85
+ * @returns {*}
86
+ */
87
+ function removeNullObjectValues(value) {
88
+ if (_.isArray(value) || !_.isObject(value)) {
89
+ return value;
90
+ }
91
+
92
+ const objectWithoutNullObjectValues = _.omit(value, objectValue => _.isNull(objectValue));
93
+
94
+ return objectWithoutNullObjectValues;
99
95
  }
100
96
 
101
- export default {areObjectsEmpty, fastMerge};
97
+ export default {removeNullObjectValues, areObjectsEmpty, fastMerge};
102
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.96",
3
+ "version": "1.0.98",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",