Skip to content

Commit 6210505

Browse files
authored
Merge pull request #171 from Expensify/marcaaron-mergeCollection
Add additional documentation for handling collections. Fix behavior of `waitForCollectionCallback`.
2 parents 1f58aa5 + 7dcd8be commit 6210505

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

API.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Subscribes a react component's state directly to a store key
7676
| [mapping.withOnyxInstance] | <code>Object</code> | whose setState() method will be called with any changed data This is used by React components to connect to Onyx |
7777
| [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 |
7878
| [mapping.initWithStoredValues] | <code>Boolean</code> | If set to false, then no data will be prefilled into the component |
79+
| [mapping.waitForCollectionCallback] | <code>Boolean</code> | If set to true, it will return the entire collection to the callback as a single object |
7980

8081
**Example**
8182
```js

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,80 @@ export default withOnyx({
137137

138138
It is preferable to use the HOC over `Onyx.connect()` in React code as `withOnyx()` will delay the rendering of the wrapped component until all keys have been accessed and made available.
139139

140+
## Collections
141+
142+
Collections allow keys with similar value types to be subscribed together by subscribing to the collection key. To define one, it must be included in the `ONYXKEYS.COLLECTION` object and it must be suffixed with an underscore. Member keys should use a unique identifier or index after the collection key prefix (e.g. `report_42`).
143+
144+
```javascript
145+
const ONYXKEYS = {
146+
COLLECTION: {
147+
REPORT: 'report_',
148+
},
149+
};
150+
```
151+
152+
### Setting Collection Values
153+
154+
To save a new collection key we can either do:
155+
156+
```js
157+
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`, report1);
158+
```
159+
160+
or we can set many at once with `mergeCollection()` (see below for guidance on best practices):
161+
162+
```js
163+
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
164+
[`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1,
165+
[`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`]: report2,
166+
[`${ONYXKEYS.COLLECTION.REPORT}${report3.reportID}`]: report3,
167+
});
168+
```
169+
170+
### Subscribing to Collections
171+
172+
There are several ways to subscribe to these keys:
173+
174+
```javascript
175+
withOnyx({
176+
allReports: {key: ONYXKEYS.COLLECTION.REPORT},
177+
})(MyComponent);
178+
```
179+
180+
This will add a prop to the component called `allReports` which is an object of collection member key/values. Changes to the individual member keys will modify the entire object and new props will be passed with each individual key update. The prop doesn't update on the initial rendering of the component until the entire collection has been read out of Onyx.
181+
182+
```js
183+
Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT}, callback: (memberValue, memberKey) => {...}});
184+
```
185+
186+
This will fire the callback once per member key depending on how many collection member keys are currently stored. Changes to those keys after the initial callbacks fire will occur when each individual key is updated.
187+
188+
```js
189+
Onyx.connect({
190+
key: ONYXKEYS.COLLECTION.REPORT,
191+
waitForCollectionCallback: true,
192+
callback: (allReports) => {...}},
193+
});
194+
```
195+
196+
This final option forces `Onyx.connect()` to behave more like `withOnyx()` and only update the callback once with the entire collection initially and later with an updated version of the collection when individual keys update.
197+
198+
### Performance Considerations When Using Collections
199+
200+
Be cautious when using collections as things can get out of hand if you have a subscriber hooked up to a collection key that has large numbers of individual keys. If this is the case, it is critical to use `mergeCollection()` over `merge()`.
201+
202+
Remember, `mergeCollection()` will notify a subscriber only *once* with the total collected values whereas each call to `merge()` would re-render a connected component *each time it is called*. Consider this example where `reports` is an array of reports that we want to index and save.
203+
204+
```js
205+
// Bad
206+
_.each(reports, report => Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report)); // -> A component using withOnyx() will have it's state updated with each iteration
207+
208+
// Good
209+
const values = {};
210+
_.each(reports, report => values[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`] = report);
211+
Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, values); // -> A component using withOnyx() will only have it's state updated once
212+
```
213+
140214
## Clean up
141215

142216
To clear all data from `Onyx` we can use `Onyx.clear()`.

lib/Onyx.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,15 @@ function keysChanged(collectionKey, collection) {
253253
return;
254254
}
255255

256+
/**
257+
* e.g. Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT, callback: ...});
258+
*/
256259
const isSubscribedToCollectionKey = isKeyMatch(subscriber.key, collectionKey)
257260
&& isCollectionKey(subscriber.key);
261+
262+
/**
263+
* e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
264+
*/
258265
const isSubscribedToCollectionMemberKey = subscriber.key.startsWith(collectionKey);
259266

260267
if (isSubscribedToCollectionKey) {
@@ -328,7 +335,15 @@ function keyChanged(key, data) {
328335
}
329336

330337
if (_.isFunction(subscriber.callback)) {
338+
if (subscriber.waitForCollectionCallback) {
339+
const cachedCollection = getCachedCollection(subscriber.key);
340+
cachedCollection[key] = data;
341+
subscriber.callback(cachedCollection);
342+
return;
343+
}
344+
331345
subscriber.callback(data, key);
346+
return;
332347
}
333348

334349
if (!subscriber.withOnyxInstance) {
@@ -397,7 +412,7 @@ function sendDataToConnection(config, val, key) {
397412
* This is used by any non-React code to connect to Onyx
398413
* @param {Boolean} [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
399414
* component
400-
* @param {Boolean} [mapping.waitForCollectionCallback] If set to true, it will trigger the callback once and return all data as a single object
415+
* @param {Boolean} [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
401416
* @returns {Number} an ID to use when calling disconnect
402417
*/
403418
function connect(mapping) {

0 commit comments

Comments
 (0)