Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
## Functions

<dl>
<dt><a href="#getSubsetOfData">getSubsetOfData(sourceData, selector)</a> ⇒ <code>Mixed</code></dt>
<dt><a href="#getSubsetOfData">getSubsetOfData(sourceData, selector, [withOnyxInstanceState])</a> ⇒ <code>Mixed</code></dt>
<dd><p>Uses a selector string or function to return a simplified version of sourceData</p>
</dd>
<dt><a href="#reduceCollectionWithSelector">reduceCollectionWithSelector(collection, selector)</a> ⇒ <code>Object</code></dt>
<dt><a href="#reduceCollectionWithSelector">reduceCollectionWithSelector(collection, selector, [withOnyxInstanceState])</a> ⇒ <code>Object</code></dt>
<dd><p>Takes a collection of items (eg. {testKey_1:{a:&#39;a&#39;}, testKey_2:{b:&#39;b&#39;}})
and runs it through a reducer function to return a subset of the data according to a selector.
The resulting collection will only contain items that are returned by the selector.</p>
Expand Down Expand Up @@ -77,19 +77,20 @@ value will be saved to storage after the default value.</p>

<a name="getSubsetOfData"></a>

## getSubsetOfData(sourceData, selector) ⇒ <code>Mixed</code>
## getSubsetOfData(sourceData, selector, [withOnyxInstanceState]) ⇒ <code>Mixed</code>
Uses a selector string or function to return a simplified version of sourceData

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| sourceData | <code>Mixed</code> | |
| selector | <code>String</code> \| <code>function</code> | If it's a string, the selector is passed to lodashGet on the sourceData If it's a function, it is passed the sourceData and it should return the simplified data |
| selector | <code>String</code> \| <code>function</code> | |
| [withOnyxInstanceState] | <code>Object</code> | If it's a string, the selector is passed to lodashGet on the sourceData If it's a function, it is passed the sourceData and it should return the simplified data |

<a name="reduceCollectionWithSelector"></a>

## reduceCollectionWithSelector(collection, selector) ⇒ <code>Object</code>
## reduceCollectionWithSelector(collection, selector, [withOnyxInstanceState]) ⇒ <code>Object</code>
Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
and runs it through a reducer function to return a subset of the data according to a selector.
The resulting collection will only contain items that are returned by the selector.
Expand All @@ -100,6 +101,7 @@ The resulting collection will only contain items that are returned by the select
| --- | --- | --- |
| collection | <code>Object</code> | |
| selector | <code>String</code> \| <code>function</code> | (see method docs for getSubsetOfData() for full details) |
| [withOnyxInstanceState] | <code>Object</code> | |

<a name="isCollectionMemberKey"></a>

Expand Down Expand Up @@ -128,7 +130,7 @@ Subscribes a react component's state directly to a store key
| [mapping.callback] | <code>function</code> | a method that will be called with changed data This is used by any non-React code to connect to Onyx |
| [mapping.initWithStoredValues] | <code>Boolean</code> | If set to false, then no data will be prefilled into the component |
| [mapping.waitForCollectionCallback] | <code>Boolean</code> | If set to true, it will return the entire collection to the callback as a single object |
| [mapping.selector] | <code>String</code> \| <code>function</code> | 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). |
| [mapping.selector] | <code>String</code> \| <code>function</code> | 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 and withOnyx state are 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). |

**Example**
```js
Expand Down
35 changes: 19 additions & 16 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ const deferredInitTask = createDeferredTask();
* Uses a selector string or function to return a simplified version of sourceData
* @param {Mixed} sourceData
* @param {String|Function} selector
* @param {Object} [withOnyxInstanceState]
* If it's a string, the selector is passed to lodashGet on the sourceData
* If it's a function, it is passed the sourceData and it should return the simplified data
* @returns {Mixed}
*/
const getSubsetOfData = (sourceData, selector) => (_.isFunction(selector)
? selector(sourceData)
const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => (_.isFunction(selector)
? selector(sourceData, withOnyxInstanceState)
: lodashGet(sourceData, selector));

/**
Expand All @@ -62,11 +63,12 @@ const getSubsetOfData = (sourceData, selector) => (_.isFunction(selector)
* The resulting collection will only contain items that are returned by the selector.
* @param {Object} collection
* @param {String|Function} selector (see method docs for getSubsetOfData() for full details)
* @param {Object} [withOnyxInstanceState]
* @returns {Object}
*/
const reduceCollectionWithSelector = (collection, selector) => _.reduce(collection, (finalCollection, item, key) => {
const reduceCollectionWithSelector = (collection, selector, withOnyxInstanceState) => _.reduce(collection, (finalCollection, item, key) => {
// eslint-disable-next-line no-param-reassign
finalCollection[key] = getSubsetOfData(item, selector);
finalCollection[key] = getSubsetOfData(item, selector, withOnyxInstanceState);

return finalCollection;
}, {});
Expand Down Expand Up @@ -362,8 +364,8 @@ function keysChanged(collectionKey, partialCollection) {
// returned by the selector.
if (subscriber.selector) {
subscriber.withOnyxInstance.setState((prevState) => {
const previousData = reduceCollectionWithSelector(prevState[subscriber.statePropertyName], subscriber.selector);
const newData = reduceCollectionWithSelector(cachedCollection, subscriber.selector);
const previousData = reduceCollectionWithSelector(prevState[subscriber.statePropertyName], subscriber.selector, subscriber.withOnyxInstance.state);
const newData = reduceCollectionWithSelector(cachedCollection, subscriber.selector, subscriber.withOnyxInstance.state);

if (!deepEqual(previousData, newData)) {
return {
Expand Down Expand Up @@ -406,7 +408,7 @@ function keysChanged(collectionKey, partialCollection) {
if (subscriber.selector) {
subscriber.withOnyxInstance.setState((prevState) => {
const prevData = prevState[subscriber.statePropertyName];
const newData = getSubsetOfData(cachedCollection[subscriber.key], subscriber.selector);
const newData = getSubsetOfData(cachedCollection[subscriber.key], subscriber.selector, subscriber.withOnyxInstance.state);
if (!deepEqual(prevData, newData)) {
PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
return {
Expand Down Expand Up @@ -488,11 +490,11 @@ function keyChanged(key, data, canUpdateSubscriber) {
subscriber.withOnyxInstance.setState((prevState) => {
const prevData = prevState[subscriber.statePropertyName];
const newData = {
[key]: getSubsetOfData(data, subscriber.selector),
[key]: getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state),
};
const prevDataWithNewData = {
...prevData,
[key]: getSubsetOfData(data, subscriber.selector),
[key]: getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state),
};
if (!deepEqual(prevData, prevDataWithNewData)) {
PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keyChanged', key);
Expand Down Expand Up @@ -523,8 +525,8 @@ function keyChanged(key, data, canUpdateSubscriber) {
// returned by the selector and only if the selected data has changed.
if (subscriber.selector) {
subscriber.withOnyxInstance.setState((prevState) => {
const previousValue = getSubsetOfData(prevState[subscriber.statePropertyName], subscriber.selector);
const newValue = getSubsetOfData(data, subscriber.selector);
const previousValue = getSubsetOfData(prevState[subscriber.statePropertyName], subscriber.selector, subscriber.withOnyxInstance.state);
const newValue = getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state);
if (!deepEqual(previousValue, newValue)) {
return {
[subscriber.statePropertyName]: newValue,
Expand Down Expand Up @@ -582,9 +584,9 @@ function sendDataToConnection(mapping, val, matchedKey) {
// returned by the selector.
if (mapping.selector) {
if (isCollectionKey(mapping.key)) {
newData = reduceCollectionWithSelector(val, mapping.selector);
newData = reduceCollectionWithSelector(val, mapping.selector, mapping.withOnyxInstance.state);
} else {
newData = getSubsetOfData(val, mapping.selector);
newData = getSubsetOfData(val, mapping.selector, mapping.withOnyxInstance.state);
}
}

Expand Down Expand Up @@ -662,9 +664,10 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
* component
* @param {Boolean} [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
* @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).
* If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData and withOnyx state are
* 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).
* @returns {Number} an ID to use when calling disconnect
*/
function connect(mapping) {
Expand Down
9 changes: 7 additions & 2 deletions tests/unit/subscribeToPropertiesTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,18 @@ describe('Only the specific property changes when using withOnyx() and ', () =>
});

it('connecting to a collection with a selector function', () => {
const mockedSelector = jest.fn(obj => obj && obj.a);
const TestComponentWithOnyx = withOnyx({
collectionWithPropertyA: {
key: ONYX_KEYS.COLLECTION.TEST_KEY,
selector: obj => obj && obj.a,
selector: mockedSelector,
},
})(ViewWithObject);
return runAllAssertionsForCollection(TestComponentWithOnyx);
return runAllAssertionsForCollection(TestComponentWithOnyx)
.then(() => {
// Check to make sure that the selector was called with the props that are passed to the rendered component
expect(mockedSelector).toHaveBeenNthCalledWith(5, {a: 'two', b: 'two'}, {loading: false, collectionWithPropertyA: {test_1: 'one', test_2: undefined}});
});
});

/**
Expand Down