react-native-onyx 1.0.18 → 1.0.20

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
@@ -6,7 +6,8 @@ import Storage from './storage';
6
6
  import * as Logger from './Logger';
7
7
  import cache from './OnyxCache';
8
8
  import createDeferredTask from './createDeferredTask';
9
- import mergeWithCustomized from './mergeWithCustomized';
9
+ import fastMerge from './fastMerge';
10
+ import * as PerformanceUtils from './metrics/PerformanceUtils';
10
11
 
11
12
  // Keeps track of the last connectionID that was used so we can keep incrementing it
12
13
  let lastConnectionID = 0;
@@ -323,12 +324,13 @@ function keysChanged(collectionKey, partialCollection) {
323
324
  if (isSubscribedToCollectionKey) {
324
325
  subscriber.withOnyxInstance.setState((prevState) => {
325
326
  const finalCollection = _.clone(prevState[subscriber.statePropertyName] || {});
326
-
327
327
  const dataKeys = _.keys(partialCollection);
328
328
  for (let j = 0; j < dataKeys.length; j++) {
329
329
  const dataKey = dataKeys[j];
330
330
  finalCollection[dataKey] = cachedCollection[dataKey];
331
331
  }
332
+
333
+ PerformanceUtils.logSetStateCall(subscriber, prevState[subscriber.statePropertyName], finalCollection, 'keysChanged', collectionKey);
332
334
  return {
333
335
  [subscriber.statePropertyName]: finalCollection,
334
336
  };
@@ -345,8 +347,17 @@ function keysChanged(collectionKey, partialCollection) {
345
347
  continue;
346
348
  }
347
349
 
348
- subscriber.withOnyxInstance.setState({
349
- [subscriber.statePropertyName]: cachedCollection[subscriber.key],
350
+ subscriber.withOnyxInstance.setState((prevState) => {
351
+ const data = cachedCollection[subscriber.key];
352
+ const previousData = prevState[subscriber.statePropertyName];
353
+ if (data === previousData) {
354
+ return null;
355
+ }
356
+
357
+ PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keysChanged', collectionKey);
358
+ return {
359
+ [subscriber.statePropertyName]: data,
360
+ };
350
361
  });
351
362
  }
352
363
  }
@@ -397,19 +408,29 @@ function keyChanged(key, data) {
397
408
  if (isCollectionKey(subscriber.key)) {
398
409
  subscriber.withOnyxInstance.setState((prevState) => {
399
410
  const collection = prevState[subscriber.statePropertyName] || {};
411
+ const newCollection = {
412
+ ...collection,
413
+ [key]: data,
414
+ };
415
+ PerformanceUtils.logSetStateCall(subscriber, collection, newCollection, 'keyChanged', key);
400
416
  return {
401
- [subscriber.statePropertyName]: {
402
- ...collection,
403
- [key]: data,
404
- },
417
+ [subscriber.statePropertyName]: newCollection,
405
418
  };
406
419
  });
407
420
  continue;
408
421
  }
409
422
 
410
423
  // If we did not match on a collection key then we just set the new data to the state property
411
- subscriber.withOnyxInstance.setState({
412
- [subscriber.statePropertyName]: data,
424
+ subscriber.withOnyxInstance.setState((prevState) => {
425
+ const previousData = prevState[subscriber.statePropertyName];
426
+ if (previousData === data) {
427
+ return null;
428
+ }
429
+
430
+ PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keyChanged', key);
431
+ return {
432
+ [subscriber.statePropertyName]: data,
433
+ };
413
434
  });
414
435
  continue;
415
436
  }
@@ -439,6 +460,7 @@ function sendDataToConnection(config, val, matchedKey) {
439
460
  }
440
461
 
441
462
  if (config.withOnyxInstance) {
463
+ PerformanceUtils.logSetStateCall(config, null, val, 'sendDataToConnection');
442
464
  config.withOnyxInstance.setWithOnyxState(config.statePropertyName, val);
443
465
  } else if (_.isFunction(config.callback)) {
444
466
  config.callback(val, matchedKey);
@@ -761,7 +783,9 @@ function applyMerge(key, data) {
761
783
  if (_.isObject(data) || _.every(mergeValues, _.isObject)) {
762
784
  // Object values are merged one after the other
763
785
  return _.reduce(mergeValues, (modifiedData, mergeValue) => {
764
- const newData = mergeWithCustomized({}, modifiedData, mergeValue);
786
+ // lodash adds a small overhead so we don't use it here
787
+ // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
788
+ const newData = Object.assign({}, fastMerge(modifiedData, mergeValue));
765
789
 
766
790
  // We will also delete any object keys that are undefined or null.
767
791
  // Deleting keys is not supported by AsyncStorage so we do it this way.
@@ -832,7 +856,7 @@ function initializeWithDefaultKeyStates() {
832
856
  .then((pairs) => {
833
857
  const asObject = _.object(pairs);
834
858
 
835
- const merged = mergeWithCustomized(asObject, defaultKeyStates);
859
+ const merged = fastMerge(asObject, defaultKeyStates);
836
860
  cache.merge(merged);
837
861
  _.each(merged, (val, key) => keyChanged(key, val));
838
862
  });
@@ -986,6 +1010,7 @@ function update(data) {
986
1010
  * of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop)
987
1011
  * @param {String[]} [option.keysToDisableSyncEvents=[]] Contains keys for which
988
1012
  * we want to disable sync event across tabs.
1013
+ * @param {Boolean} [options.debugSetState] Enables debugging setState() calls to connected components.
989
1014
  * @example
990
1015
  * Onyx.init({
991
1016
  * keys: ONYXKEYS,
@@ -1002,6 +1027,7 @@ function init({
1002
1027
  captureMetrics = false,
1003
1028
  shouldSyncMultipleInstances = Boolean(global.localStorage),
1004
1029
  keysToDisableSyncEvents = [],
1030
+ debugSetState = false,
1005
1031
  } = {}) {
1006
1032
  if (captureMetrics) {
1007
1033
  // The code here is only bundled and applied when the captureMetrics is set
@@ -1009,6 +1035,10 @@ function init({
1009
1035
  applyDecorators();
1010
1036
  }
1011
1037
 
1038
+ if (debugSetState) {
1039
+ PerformanceUtils.setShouldDebugSetState(true);
1040
+ }
1041
+
1012
1042
  if (maxCachedKeysCount > 0) {
1013
1043
  cache.setRecentKeysLimit(maxCachedKeysCount);
1014
1044
  }
package/lib/OnyxCache.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import _ from 'underscore';
2
- import mergeWithCustomized from './mergeWithCustomized';
2
+ import fastMerge from './fastMerge';
3
3
 
4
4
  const isDefined = _.negate(_.isUndefined);
5
5
 
@@ -110,7 +110,9 @@ class OnyxCache {
110
110
  * @param {Record<string, *>} data - a map of (cache) key - values
111
111
  */
112
112
  merge(data) {
113
- this.storageMap = mergeWithCustomized({}, this.storageMap, data);
113
+ // lodash adds a small overhead so we don't use it here
114
+ // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
115
+ this.storageMap = Object.assign({}, fastMerge(this.storageMap, data));
114
116
 
115
117
  const storageKeys = this.getAllKeys();
116
118
  const mergedKeys = _.keys(data);
@@ -0,0 +1,66 @@
1
+ // Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1
2
+
3
+ /**
4
+ * @param {mixed} val
5
+ * @returns {boolean}
6
+ */
7
+ function isMergeableObject(val) {
8
+ const nonNullObject = val != null ? typeof val === 'object' : false;
9
+ return (nonNullObject
10
+ && Object.prototype.toString.call(val) !== '[object RegExp]'
11
+ && Object.prototype.toString.call(val) !== '[object Date]');
12
+ }
13
+
14
+ /**
15
+ * @param {Object} target
16
+ * @param {Object} source
17
+ * @returns {Object}
18
+ */
19
+ function mergeObject(target, source) {
20
+ const destination = {};
21
+ if (isMergeableObject(target)) {
22
+ // lodash adds a small overhead so we don't use it here
23
+ // eslint-disable-next-line rulesdir/prefer-underscore-method
24
+ const targetKeys = Object.keys(target);
25
+ for (let i = 0; i < targetKeys.length; ++i) {
26
+ const key = targetKeys[i];
27
+ destination[key] = target[key];
28
+ }
29
+ }
30
+
31
+ // lodash adds a small overhead so we don't use it here
32
+ // eslint-disable-next-line rulesdir/prefer-underscore-method
33
+ const sourceKeys = Object.keys(source);
34
+ for (let i = 0; i < sourceKeys.length; ++i) {
35
+ const key = sourceKeys[i];
36
+ if (source[key] === undefined) {
37
+ // eslint-disable-next-line no-continue
38
+ continue;
39
+ }
40
+ if (!isMergeableObject(source[key]) || !target[key]) {
41
+ destination[key] = source[key];
42
+ } else {
43
+ // eslint-disable-next-line no-use-before-define
44
+ destination[key] = fastMerge(target[key], source[key]);
45
+ }
46
+ }
47
+
48
+ return destination;
49
+ }
50
+
51
+ /**
52
+ * @param {Object|Array} target
53
+ * @param {Object|Array} source
54
+ * @returns {Object|Array}
55
+ */
56
+ function fastMerge(target, source) {
57
+ // lodash adds a small overhead so we don't use it here
58
+ // eslint-disable-next-line rulesdir/prefer-underscore-method
59
+ const array = Array.isArray(source);
60
+ if (array) {
61
+ return source;
62
+ }
63
+ return mergeObject(target, source);
64
+ }
65
+
66
+ export default fastMerge;
@@ -0,0 +1,68 @@
1
+ import lodashTransform from 'lodash/transform';
2
+ import _ from 'underscore';
3
+
4
+ let debugSetState = false;
5
+
6
+ /**
7
+ * @param {Boolean} debug
8
+ */
9
+ function setShouldDebugSetState(debug) {
10
+ debugSetState = debug;
11
+ }
12
+
13
+ /**
14
+ * Deep diff between two objects. Useful for figuring out what changed about an object from one render to the next so
15
+ * that state and props updates can be optimized.
16
+ *
17
+ * @param {Object} object
18
+ * @param {Object} base
19
+ * @return {Object}
20
+ */
21
+ function diffObject(object, base) {
22
+ function changes(obj, comparisonObject) {
23
+ return lodashTransform(obj, (result, value, key) => {
24
+ if (_.isEqual(value, comparisonObject[key])) {
25
+ return;
26
+ }
27
+
28
+ // eslint-disable-next-line no-param-reassign
29
+ result[key] = (_.isObject(value) && _.isObject(comparisonObject[key]))
30
+ ? changes(value, comparisonObject[key])
31
+ : value;
32
+ });
33
+ }
34
+ return changes(object, base);
35
+ }
36
+
37
+ /**
38
+ * Provide insights into why a setState() call occurred by diffing the before and after values.
39
+ *
40
+ * @param {Object} mapping
41
+ * @param {*} previousValue
42
+ * @param {*} newValue
43
+ * @param {String} caller
44
+ * @param {String} [keyThatChanged]
45
+ */
46
+ function logSetStateCall(mapping, previousValue, newValue, caller, keyThatChanged) {
47
+ if (!debugSetState) {
48
+ return;
49
+ }
50
+
51
+ const logParams = {};
52
+ if (keyThatChanged) {
53
+ logParams.keyThatChanged = keyThatChanged;
54
+ }
55
+ if (_.isObject(newValue) && _.isObject(previousValue)) {
56
+ logParams.difference = diffObject(previousValue, newValue);
57
+ } else {
58
+ logParams.previousValue = previousValue;
59
+ logParams.newValue = newValue;
60
+ }
61
+
62
+ console.debug(`[Onyx-Debug] ${mapping.displayName} setState() called. Subscribed to key '${mapping.key}' (${caller})`, logParams);
63
+ }
64
+
65
+ export {
66
+ logSetStateCall,
67
+ setShouldDebugSetState,
68
+ };
@@ -7,7 +7,7 @@
7
7
  import localforage from 'localforage';
8
8
  import _ from 'underscore';
9
9
  import SyncQueue from '../../SyncQueue';
10
- import mergeWithCustomized from '../../mergeWithCustomized';
10
+ import fastMerge from '../../fastMerge';
11
11
 
12
12
  localforage.config({
13
13
  name: 'OnyxDB',
@@ -24,7 +24,10 @@ const provider = {
24
24
  return localforage.getItem(key)
25
25
  .then((existingValue) => {
26
26
  const newValue = _.isObject(existingValue)
27
- ? mergeWithCustomized({}, existingValue, value)
27
+
28
+ // lodash adds a small overhead so we don't use it here
29
+ // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
30
+ ? Object.assign({}, fastMerge(existingValue, value))
28
31
  : value;
29
32
  return localforage.setItem(key, newValue);
30
33
  });
package/lib/withOnyx.js CHANGED
@@ -25,8 +25,8 @@ export default function (mapOnyxToState) {
25
25
  .omit(config => config.initWithStoredValues === false)
26
26
  .keys()
27
27
  .value();
28
-
29
28
  return (WrappedComponent) => {
29
+ const displayName = getDisplayName(WrappedComponent);
30
30
  class withOnyx extends React.Component {
31
31
  constructor(props) {
32
32
  super(props);
@@ -152,6 +152,7 @@ export default function (mapOnyxToState) {
152
152
  key,
153
153
  statePropertyName,
154
154
  withOnyxInstance: this,
155
+ displayName,
155
156
  });
156
157
  }
157
158
 
@@ -190,7 +191,7 @@ export default function (mapOnyxToState) {
190
191
  withOnyx.defaultProps = {
191
192
  forwardedRef: undefined,
192
193
  };
193
- withOnyx.displayName = `withOnyx(${getDisplayName(WrappedComponent)})`;
194
+ withOnyx.displayName = `withOnyx(${displayName})`;
194
195
  return React.forwardRef((props, ref) => {
195
196
  const Component = withOnyx;
196
197
  // eslint-disable-next-line react/jsx-props-no-spreading
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -1,26 +0,0 @@
1
- import lodashMergeWith from 'lodash/mergeWith';
2
-
3
- /**
4
- * When merging 2 objects into onyx that contain an array, we want to completely replace the array instead of the default
5
- * behavior which is to merge each item by its index.
6
- * ie:
7
- * merge({a: [1]}, {a: [2,3]}):
8
- * - In the default implementation would produce {a:[1,3]}
9
- * - With this function would produce: {a: [2,3]}
10
- * @param {*} objValue
11
- * @param {*} srcValue
12
- * @return {*}
13
- */
14
- // eslint-disable-next-line rulesdir/prefer-early-return
15
- function customizerForMergeWith(objValue, srcValue) {
16
- // eslint-disable-next-line rulesdir/prefer-underscore-method
17
- if (Array.isArray(objValue)) {
18
- return srcValue;
19
- }
20
- }
21
-
22
- function mergeWithCustomized(...args) {
23
- return lodashMergeWith(...args, customizerForMergeWith);
24
- }
25
-
26
- export default mergeWithCustomized;