react-native-onyx 1.0.57 → 1.0.59

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
@@ -909,6 +909,24 @@ function hasPendingMergeForKey(key) {
909
909
  return Boolean(mergeQueue[key]);
910
910
  }
911
911
 
912
+ /**
913
+ * 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.
914
+ * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
915
+ * To be consistent with the behaviour for merge, we'll also want to remove nullish values for "set" operations.
916
+ * On web, IndexedDB will keep the top-level keys along with a null value and this uses up storage and memory.
917
+ * 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.
918
+ * @private
919
+ * @param {*} value
920
+ * @returns {*}
921
+ */
922
+ function removeNullObjectValues(value) {
923
+ if (_.isArray(value) || !_.isObject(value)) {
924
+ return value;
925
+ }
926
+
927
+ return _.omit(value, objectValue => _.isNull(objectValue));
928
+ }
929
+
912
930
  /**
913
931
  * Write a value to our store with the given key
914
932
  *
@@ -926,18 +944,20 @@ function set(key, value) {
926
944
  Logger.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
927
945
  }
928
946
 
929
- const hasChanged = cache.hasValueChanged(key, value);
947
+ const valueWithNullRemoved = removeNullObjectValues(value);
948
+
949
+ const hasChanged = cache.hasValueChanged(key, valueWithNullRemoved);
930
950
 
931
951
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
932
- broadcastUpdate(key, value, hasChanged, 'set');
952
+ broadcastUpdate(key, valueWithNullRemoved, hasChanged, 'set');
933
953
 
934
954
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
935
955
  if (!hasChanged) {
936
956
  return Promise.resolve();
937
957
  }
938
958
 
939
- return Storage.setItem(key, value)
940
- .catch(error => evictStorageAndRetry(error, set, key, value));
959
+ return Storage.setItem(key, valueWithNullRemoved)
960
+ .catch(error => evictStorageAndRetry(error, set, key, valueWithNullRemoved));
941
961
  }
942
962
 
943
963
  /**
@@ -1034,19 +1054,21 @@ function merge(key, changes) {
1034
1054
  .then((existingValue) => {
1035
1055
  try {
1036
1056
  // 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)
1037
- const batchedChanges = applyMerge(undefined, mergeQueue[key]);
1057
+ let batchedChanges = applyMerge(undefined, mergeQueue[key]);
1038
1058
 
1039
1059
  // Clean up the write queue so we
1040
1060
  // don't apply these changes again
1041
1061
  delete mergeQueue[key];
1042
1062
 
1043
1063
  // After that we merge the batched changes with the existing value
1044
- let modifiedData = applyMerge(existingValue, [batchedChanges]);
1045
-
1046
- // For objects, the key for null values needs to be removed from the object to ensure the value will get removed from storage completely.
1047
- // On native, SQLite will remove top-level keys that are null. To be consistent, we remove them on web too.
1048
- if (!_.isArray(modifiedData) && _.isObject(modifiedData)) {
1049
- modifiedData = _.omit(modifiedData, value => _.isNull(value));
1064
+ const modifiedData = removeNullObjectValues(applyMerge(existingValue, [batchedChanges]));
1065
+
1066
+ // On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
1067
+ // JSON_PATCH generally removes top-level nullish values from the stored object.
1068
+ // 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.
1069
+ // Therefore we need to remove nullish values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1070
+ if (!existingValue) {
1071
+ batchedChanges = removeNullObjectValues(batchedChanges);
1050
1072
  }
1051
1073
 
1052
1074
  const hasChanged = cache.hasValueChanged(key, modifiedData);
@@ -1376,7 +1398,6 @@ const Onyx = {
1376
1398
  multiSet,
1377
1399
  merge,
1378
1400
  mergeCollection,
1379
- hasPendingMergeForKey,
1380
1401
  update,
1381
1402
  clear,
1382
1403
  getAllKeys,
package/lib/withOnyx.js CHANGED
@@ -213,7 +213,8 @@ export default function (mapOnyxToState) {
213
213
  withOnyx.propTypes = {
214
214
  forwardedRef: PropTypes.oneOfType([
215
215
  PropTypes.func,
216
- PropTypes.shape({current: PropTypes.instanceOf(React.Component)}),
216
+ // eslint-disable-next-line react/forbid-prop-types
217
+ PropTypes.shape({current: PropTypes.object}),
217
218
  ]),
218
219
  };
219
220
  withOnyx.defaultProps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.57",
3
+ "version": "1.0.59",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",