react-native-onyx 1.0.19 → 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
@@ -7,6 +7,7 @@ import * as Logger from './Logger';
7
7
  import cache from './OnyxCache';
8
8
  import createDeferredTask from './createDeferredTask';
9
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);
@@ -988,6 +1010,7 @@ function update(data) {
988
1010
  * of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop)
989
1011
  * @param {String[]} [option.keysToDisableSyncEvents=[]] Contains keys for which
990
1012
  * we want to disable sync event across tabs.
1013
+ * @param {Boolean} [options.debugSetState] Enables debugging setState() calls to connected components.
991
1014
  * @example
992
1015
  * Onyx.init({
993
1016
  * keys: ONYXKEYS,
@@ -1004,6 +1027,7 @@ function init({
1004
1027
  captureMetrics = false,
1005
1028
  shouldSyncMultipleInstances = Boolean(global.localStorage),
1006
1029
  keysToDisableSyncEvents = [],
1030
+ debugSetState = false,
1007
1031
  } = {}) {
1008
1032
  if (captureMetrics) {
1009
1033
  // The code here is only bundled and applied when the captureMetrics is set
@@ -1011,6 +1035,10 @@ function init({
1011
1035
  applyDecorators();
1012
1036
  }
1013
1037
 
1038
+ if (debugSetState) {
1039
+ PerformanceUtils.setShouldDebugSetState(true);
1040
+ }
1041
+
1014
1042
  if (maxCachedKeysCount > 0) {
1015
1043
  cache.setRecentKeysLimit(maxCachedKeysCount);
1016
1044
  }
@@ -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
+ };
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.19",
3
+ "version": "1.0.20",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",