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/README.md +3 -0
- package/dist/web.development.js +206 -58
- package/dist/web.development.js.map +1 -1
- package/dist/web.min.js +1 -1
- package/dist/web.min.js.map +1 -1
- package/lib/Onyx.js +42 -12
- package/lib/OnyxCache.js +4 -2
- package/lib/fastMerge.js +66 -0
- package/lib/metrics/PerformanceUtils.js +68 -0
- package/lib/storage/providers/LocalForage.js +5 -2
- package/lib/withOnyx.js +3 -2
- package/package.json +1 -1
- package/lib/mergeWithCustomized.js +0 -26
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
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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);
|
package/lib/fastMerge.js
ADDED
|
@@ -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
|
|
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
|
-
|
|
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(${
|
|
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,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;
|