react-native-onyx 1.0.24 → 1.0.25
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/API.md +56 -21
- package/dist/web.development.js +142 -22
- 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 +131 -11
- package/package.json +1 -1
package/lib/Onyx.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-continue */
|
|
2
2
|
import _ from 'underscore';
|
|
3
3
|
import Str from 'expensify-common/lib/str';
|
|
4
|
+
import {deepEqual} from 'fast-equals';
|
|
4
5
|
import lodashGet from 'lodash/get';
|
|
5
6
|
import Storage from './storage';
|
|
6
7
|
import * as Logger from './Logger';
|
|
@@ -35,6 +36,33 @@ let defaultKeyStates = {};
|
|
|
35
36
|
// Connections can be made before `Onyx.init`. They would wait for this task before resolving
|
|
36
37
|
const deferredInitTask = createDeferredTask();
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Uses a selector string or function to return a simplified version of sourceData
|
|
41
|
+
* @param {Mixed} sourceData
|
|
42
|
+
* @param {String|Function} selector
|
|
43
|
+
* If it's a string, the selector is passed to lodashGet on the sourceData
|
|
44
|
+
* If it's a function, it is passed the sourceData and it should return the simplified data
|
|
45
|
+
* @returns {Mixed}
|
|
46
|
+
*/
|
|
47
|
+
const getSubsetOfData = (sourceData, selector) => (_.isFunction(selector)
|
|
48
|
+
? selector(sourceData)
|
|
49
|
+
: lodashGet(sourceData, selector));
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
|
|
53
|
+
* and runs it through a reducer function to return a subset of the data according to a selector.
|
|
54
|
+
* The resulting collection will only contain items that are returned by the selector.
|
|
55
|
+
* @param {Object} collection
|
|
56
|
+
* @param {String|Function} selector (see method docs for getSubsetOfData() for full details)
|
|
57
|
+
* @returns {Object}
|
|
58
|
+
*/
|
|
59
|
+
const reduceCollectionWithSelector = (collection, selector) => _.reduce(collection, (finalCollection, item, key) => {
|
|
60
|
+
// eslint-disable-next-line no-param-reassign
|
|
61
|
+
finalCollection[key] = getSubsetOfData(item, selector);
|
|
62
|
+
|
|
63
|
+
return finalCollection;
|
|
64
|
+
}, {});
|
|
65
|
+
|
|
38
66
|
/**
|
|
39
67
|
* Get some data from the store
|
|
40
68
|
*
|
|
@@ -322,6 +350,23 @@ function keysChanged(collectionKey, partialCollection) {
|
|
|
322
350
|
// We are subscribed to a collection key so we must update the data in state with the new
|
|
323
351
|
// collection member key values from the partial update.
|
|
324
352
|
if (isSubscribedToCollectionKey) {
|
|
353
|
+
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
354
|
+
// returned by the selector.
|
|
355
|
+
if (subscriber.selector) {
|
|
356
|
+
subscriber.withOnyxInstance.setState((prevState) => {
|
|
357
|
+
const previousData = reduceCollectionWithSelector(prevState[subscriber.statePropertyName], subscriber.selector);
|
|
358
|
+
const newData = reduceCollectionWithSelector(cachedCollection, subscriber.selector);
|
|
359
|
+
|
|
360
|
+
if (!deepEqual(previousData, newData)) {
|
|
361
|
+
return {
|
|
362
|
+
[subscriber.statePropertyName]: newData,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
});
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
325
370
|
subscriber.withOnyxInstance.setState((prevState) => {
|
|
326
371
|
const finalCollection = _.clone(prevState[subscriber.statePropertyName] || {});
|
|
327
372
|
const dataKeys = _.keys(partialCollection);
|
|
@@ -347,6 +392,25 @@ function keysChanged(collectionKey, partialCollection) {
|
|
|
347
392
|
continue;
|
|
348
393
|
}
|
|
349
394
|
|
|
395
|
+
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
396
|
+
// returned by the selector and the state should only change when the subset of data changes from what
|
|
397
|
+
// it was previously.
|
|
398
|
+
if (subscriber.selector) {
|
|
399
|
+
subscriber.withOnyxInstance.setState((prevState) => {
|
|
400
|
+
const prevData = prevState[subscriber.statePropertyName];
|
|
401
|
+
const newData = getSubsetOfData(cachedCollection[subscriber.key], subscriber.selector);
|
|
402
|
+
if (!deepEqual(prevData, newData)) {
|
|
403
|
+
PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
|
|
404
|
+
return {
|
|
405
|
+
[subscriber.statePropertyName]: newData,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return null;
|
|
410
|
+
});
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
|
|
350
414
|
subscriber.withOnyxInstance.setState((prevState) => {
|
|
351
415
|
const data = cachedCollection[subscriber.key];
|
|
352
416
|
const previousData = prevState[subscriber.statePropertyName];
|
|
@@ -410,6 +474,29 @@ function keyChanged(key, data, canUpdateSubscriber) {
|
|
|
410
474
|
if (subscriber.withOnyxInstance) {
|
|
411
475
|
// Check if we are subscribing to a collection key and overwrite the collection member key value in state
|
|
412
476
|
if (isCollectionKey(subscriber.key)) {
|
|
477
|
+
// If the subscriber has a selector, then the consumer of this data must only be given the data
|
|
478
|
+
// returned by the selector and only when the selected data has changed.
|
|
479
|
+
if (subscriber.selector) {
|
|
480
|
+
subscriber.withOnyxInstance.setState((prevState) => {
|
|
481
|
+
const prevData = prevState[subscriber.statePropertyName];
|
|
482
|
+
const newData = {
|
|
483
|
+
[key]: getSubsetOfData(data, subscriber.selector),
|
|
484
|
+
};
|
|
485
|
+
const prevDataWithNewData = {
|
|
486
|
+
...prevData,
|
|
487
|
+
[key]: getSubsetOfData(data, subscriber.selector),
|
|
488
|
+
};
|
|
489
|
+
if (!deepEqual(prevData, prevDataWithNewData)) {
|
|
490
|
+
PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keyChanged', key);
|
|
491
|
+
return {
|
|
492
|
+
[subscriber.statePropertyName]: prevDataWithNewData,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
});
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
413
500
|
subscriber.withOnyxInstance.setState((prevState) => {
|
|
414
501
|
const collection = prevState[subscriber.statePropertyName] || {};
|
|
415
502
|
const newCollection = {
|
|
@@ -424,6 +511,22 @@ function keyChanged(key, data, canUpdateSubscriber) {
|
|
|
424
511
|
continue;
|
|
425
512
|
}
|
|
426
513
|
|
|
514
|
+
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
515
|
+
// returned by the selector and only if the selected data has changed.
|
|
516
|
+
if (subscriber.selector) {
|
|
517
|
+
subscriber.withOnyxInstance.setState((prevState) => {
|
|
518
|
+
const previousValue = getSubsetOfData(prevState, subscriber.selector);
|
|
519
|
+
const newValue = getSubsetOfData(data, subscriber.selector);
|
|
520
|
+
if (!deepEqual(previousValue, newValue)) {
|
|
521
|
+
return {
|
|
522
|
+
[subscriber.statePropertyName]: newValue,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
});
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
|
|
427
530
|
// If we did not match on a collection key then we just set the new data to the state property
|
|
428
531
|
subscriber.withOnyxInstance.setState((prevState) => {
|
|
429
532
|
const previousData = prevState[subscriber.statePropertyName];
|
|
@@ -449,25 +552,41 @@ function keyChanged(key, data, canUpdateSubscriber) {
|
|
|
449
552
|
* - triggers the callback function
|
|
450
553
|
*
|
|
451
554
|
* @private
|
|
452
|
-
* @param {
|
|
453
|
-
* @param {
|
|
454
|
-
* @param {
|
|
455
|
-
* @param {
|
|
555
|
+
* @param {Object} mapping
|
|
556
|
+
* @param {Object} [mapping.withOnyxInstance]
|
|
557
|
+
* @param {String} [mapping.statePropertyName]
|
|
558
|
+
* @param {Function} [mapping.callback]
|
|
559
|
+
* @param {String} [mapping.selector]
|
|
456
560
|
* @param {*|null} val
|
|
457
561
|
* @param {String} matchedKey
|
|
458
562
|
*/
|
|
459
|
-
function sendDataToConnection(
|
|
563
|
+
function sendDataToConnection(mapping, val, matchedKey) {
|
|
460
564
|
// If the mapping no longer exists then we should not send any data.
|
|
461
565
|
// This means our subscriber disconnected or withOnyx wrapped component unmounted.
|
|
462
|
-
if (!callbackToStateMapping[
|
|
566
|
+
if (!callbackToStateMapping[mapping.connectionID]) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (mapping.withOnyxInstance) {
|
|
571
|
+
let newData = val;
|
|
572
|
+
|
|
573
|
+
// If the mapping has a selector, then the component's state must only be updated with the data
|
|
574
|
+
// returned by the selector.
|
|
575
|
+
if (mapping.selector) {
|
|
576
|
+
if (isCollectionKey(mapping.key)) {
|
|
577
|
+
newData = reduceCollectionWithSelector(val, mapping.selector);
|
|
578
|
+
} else {
|
|
579
|
+
newData = getSubsetOfData(val, mapping.selector);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
PerformanceUtils.logSetStateCall(mapping, null, newData, 'sendDataToConnection');
|
|
584
|
+
mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
|
|
463
585
|
return;
|
|
464
586
|
}
|
|
465
587
|
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
config.withOnyxInstance.setWithOnyxState(config.statePropertyName, val);
|
|
469
|
-
} else if (_.isFunction(config.callback)) {
|
|
470
|
-
config.callback(val, matchedKey);
|
|
588
|
+
if (_.isFunction(mapping.callback)) {
|
|
589
|
+
mapping.callback(val, matchedKey);
|
|
471
590
|
}
|
|
472
591
|
}
|
|
473
592
|
|
|
@@ -534,6 +653,7 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
|
|
|
534
653
|
* @param {Boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
|
|
535
654
|
* component
|
|
536
655
|
* @param {Boolean} [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
|
|
656
|
+
* @param {String|Function} [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data. If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData is passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
537
657
|
* @returns {Number} an ID to use when calling disconnect
|
|
538
658
|
*/
|
|
539
659
|
function connect(mapping) {
|