Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional documentation for handling collections. Fix behavior of waitForCollectionCallback. #171

Merged
merged 4 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Subscribes a react component's state directly to a store key
| [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 |
| [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 |

**Example**
```js
Expand Down
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,70 @@ export default withOnyx({

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.

## Collections

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. `chat_42`).
luacmartins marked this conversation as resolved.
Show resolved Hide resolved

```javascript
const ONYXKEYS = {
COLLECTION: {
CHAT: 'chat_',
},
};
```

### Setting Collection Values

To save a new collection key we can either do:

```js
Onyx.merge(`${ONYXKEYS.COLLECTION.CHAT}${chat1.id}`, chat1);
```

or we can set many at once with `mergeCollection()`:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a little more guidance here so people know which one to choose? I think adding information to say that when there are multiple keys, you should always use mergeCollect() because it optimizes all the UI updates. If it's a single key, it's fine to use either.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see you added all that information below. Maybe just add something here that says "See below for guidance on best practices"


```js
Onyx.mergeCollection(ONYXKEYS.COLLECTION.CHAT, {
[`${ONYXKEYS.COLLECTION.CHAT}${chat1.id}`]: chat1,
[`${ONYXKEYS.COLLECTION.CHAT}${chat2.id}`]: chat2,
[`${ONYXKEYS.COLLECTION.CHAT}${chat3.id}`]: chat3,
});
```

### Subscribing to Collections

There are several ways to subscribe to these keys:

```javascript
withOnyx({
allChats: {key: ONYXKEYS.COLLECTION.CHAT},
})(MyComponent);
```

This will return the initial collection as 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will return the initial collection as 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.
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


```js
Onyx.connect({key: ONYXKEYS.COLLECTION.CHAT}, callback: (memberValue, memberKey) => {...}});
```

This will fire the callback `n` times depending on how many collection member keys have been stored. Changes to those keys after the initial callbacks fire will occur when each individual key is updated.
marcaaron marked this conversation as resolved.
Show resolved Hide resolved

```js
Onyx.connect({
key: ONYXKEYS.COLLECTION.CHAT,
waitForCollectionCallback: true,
callback: (allChats) => {...}},
});
```

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.

### Performance Considerations When Using Collections

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()`.

Remember, `mergeCollection()` will notify a subscriber only *once* with the total collected values whereas each call to `Onyx.merge()` would re-render a connected component `n` times per key that you call it on.
marcaaron marked this conversation as resolved.
Show resolved Hide resolved

## Clean up

To clear all data from `Onyx` we can use `Onyx.clear()`.
Expand Down
17 changes: 16 additions & 1 deletion lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,15 @@ function keysChanged(collectionKey, collection) {
return;
}

/**
* e.g. Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT, callback: ...});
*/
const isSubscribedToCollectionKey = isKeyMatch(subscriber.key, collectionKey)
&& isCollectionKey(subscriber.key);

/**
* e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
*/
const isSubscribedToCollectionMemberKey = subscriber.key.startsWith(collectionKey);

if (isSubscribedToCollectionKey) {
Expand Down Expand Up @@ -328,7 +335,15 @@ function keyChanged(key, data) {
}

if (_.isFunction(subscriber.callback)) {
if (subscriber.waitForCollectionCallback) {
const cachedCollection = getCachedCollection(subscriber.key);
cachedCollection[key] = data;
subscriber.callback(cachedCollection);
return;
}

subscriber.callback(data, key);
return;
}

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