Skip to content

Commit c154ed4

Browse files
committed
Merge branch 'feature/onyx-store-poc-baseline' into feature/onyx-store-pr-2
2 parents 2217ee0 + f3fc3b6 commit c154ed4

15 files changed

Lines changed: 147 additions & 362 deletions

API.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ This method will be deprecated soon. Please use `Onyx.connectWithoutView()` inst
8080
| connectOptions | The options object that will define the behavior of the connection. |
8181
| connectOptions.key | The Onyx key to subscribe to. |
8282
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
83-
| connectOptions.waitForCollectionCallback | If set to `true`, it will return the entire collection to the callback as a single object. |
8483
| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.** Using this setting on `useOnyx()` 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). |
8584

8685
**Example**
@@ -103,7 +102,6 @@ Connects to an Onyx key given the options passed and listens to its changes.
103102
| connectOptions | The options object that will define the behavior of the connection. |
104103
| connectOptions.key | The Onyx key to subscribe to. |
105104
| connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
106-
| connectOptions.waitForCollectionCallback | If set to `true`, it will return the entire collection to the callback as a single object. |
107105
| connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.** Using this setting on `useOnyx()` 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). |
108106

109107
**Example**

README.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,20 +257,10 @@ export default MyComponent;
257257
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.
258258

259259
```js
260-
Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT}, callback: (memberValue, memberKey) => {...});
260+
Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT}, callback: (allReports, collectionKey) => {...});
261261
```
262262

263-
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.
264-
265-
```js
266-
Onyx.connect({
267-
key: ONYXKEYS.COLLECTION.REPORT,
268-
waitForCollectionCallback: true,
269-
callback: (allReports, collectionKey) => {...},
270-
});
271-
```
272-
273-
This final option forces `Onyx.connect()` to behave more like `useOnyx()` and only update the callback once with the entire collection initially and later with an updated version of the collection when individual keys update.
263+
This will fire the callback once with the entire collection initially and later with an updated version of the collection when individual keys update.
274264

275265
### Performance Considerations When Using Collections
276266

lib/Onyx.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ function init({
9494
* @param connectOptions The options object that will define the behavior of the connection.
9595
* @param connectOptions.key The Onyx key to subscribe to.
9696
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
97-
* @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
9897
* @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.**
9998
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
10099
* when the subset of data changes. Otherwise, any change of data on any property would normally
@@ -119,7 +118,6 @@ function connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): Co
119118
* @param connectOptions The options object that will define the behavior of the connection.
120119
* @param connectOptions.key The Onyx key to subscribe to.
121120
* @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
122-
* @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
123121
* @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook.**
124122
* Using this setting on `useOnyx()` can have very positive performance benefits because the component will only re-render
125123
* when the subset of data changes. Otherwise, any change of data on any property would normally

lib/OnyxConnectionManager.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {ConnectOptions} from './Onyx';
44
import OnyxUtils from './OnyxUtils';
55
import OnyxKeys from './OnyxKeys';
66
import * as Str from './Str';
7-
import type {CollectionConnectCallback, DefaultConnectCallback, DefaultConnectOptions, OnyxKey, OnyxValue} from './types';
7+
import type {CollectionConnectCallback, DefaultConnectCallback, OnyxKey, OnyxValue} from './types';
88
import onyxSnapshotCache from './OnyxSnapshotCache';
99

1010
type ConnectCallback = DefaultConnectCallback<OnyxKey> | CollectionConnectCallback<OnyxKey>;
@@ -43,11 +43,6 @@ type ConnectionMetadata = {
4343
* The last callback key returned by `OnyxUtils.subscribeToKey()`'s callback.
4444
*/
4545
cachedCallbackKey?: OnyxKey;
46-
47-
/**
48-
* Whether the subscriber is waiting for the collection callback to be fired.
49-
*/
50-
waitForCollectionCallback?: boolean;
5146
};
5247

5348
/**
@@ -110,21 +105,20 @@ class OnyxConnectionManager {
110105
* according to their purpose and effect they produce in the Onyx connection.
111106
*/
112107
private generateConnectionID<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): string {
113-
const {key, reuseConnection, waitForCollectionCallback} = connectOptions;
108+
const {key, reuseConnection} = connectOptions;
114109

115110
// The current session ID is appended to the connection ID so we can have different connections
116111
// after an `Onyx.clear()` operation.
117112
let suffix = `,sessionID=${this.sessionID}`;
118113

119-
// We will generate a unique ID in any of the following situations:
120-
// - `reuseConnection` is `false`. That means the subscriber explicitly wants the connection to not be reused.
121-
// - `key` is a collection key AND `waitForCollectionCallback` is `undefined/false`. This combination needs a new connection at every subscription
122-
// in order to send all the collection entries, so the connection can't be reused.
123-
if (reuseConnection === false || (OnyxKeys.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false))) {
114+
// We will generate a unique ID when `reuseConnection` is `false`, which means the subscriber
115+
// explicitly wants the connection to not be reused. Collection-root subscriptions are now always
116+
// snapshot mode, so they can be reused like any other connection.
117+
if (reuseConnection === false) {
124118
suffix += `,uniqueID=${Str.guid()}`;
125119
}
126120

127-
return `onyxKey=${key},waitForCollectionCallback=${waitForCollectionCallback ?? false}${suffix}`;
121+
return `onyxKey=${key}${suffix}`;
128122
}
129123

130124
/**
@@ -137,10 +131,14 @@ class OnyxConnectionManager {
137131
}
138132

139133
for (const callback of connection.callbacks.values()) {
140-
if (connection.waitForCollectionCallback) {
141-
(callback as CollectionConnectCallback<OnyxKey>)(connection.cachedCallbackValue as Record<string, unknown>, connection.cachedCallbackKey as OnyxKey);
142-
} else {
143-
(callback as DefaultConnectCallback<OnyxKey>)(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey);
134+
try {
135+
if (OnyxKeys.isCollectionKey(connection.onyxKey)) {
136+
(callback as CollectionConnectCallback<OnyxKey>)(connection.cachedCallbackValue as Record<string, unknown>, connection.cachedCallbackKey as OnyxKey);
137+
} else {
138+
(callback as DefaultConnectCallback<OnyxKey>)(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey);
139+
}
140+
} catch (error) {
141+
Logger.logAlert(`[ConnectionManager] Subscriber callback threw an error for key '${connection.onyxKey}': ${error}`);
144142
}
145143
}
146144
}
@@ -174,15 +172,14 @@ class OnyxConnectionManager {
174172

175173
subscriptionID = OnyxUtils.subscribeToKey({
176174
...connectOptions,
177-
callback: callback as DefaultConnectCallback<TKey>,
178-
});
175+
callback,
176+
} as ConnectOptions<TKey>);
179177

180178
connectionMetadata = {
181179
subscriptionID,
182180
onyxKey: connectOptions.key,
183181
isConnectionMade: false,
184182
callbacks: new Map(),
185-
waitForCollectionCallback: connectOptions.waitForCollectionCallback,
186183
};
187184

188185
this.connectionsMap.set(connectionID, connectionMetadata);
@@ -198,7 +195,7 @@ class OnyxConnectionManager {
198195
// Defer the callback execution to the next tick of the event loop.
199196
// This ensures that the current execution flow completes and the result connection object is available when the callback fires.
200197
Promise.resolve().then(() => {
201-
(connectOptions as DefaultConnectOptions<OnyxKey>).callback?.(connectionMetadata.cachedCallbackValue, connectionMetadata.cachedCallbackKey as OnyxKey);
198+
(connectOptions.callback as DefaultConnectCallback<OnyxKey> | undefined)?.(connectionMetadata.cachedCallbackValue, connectionMetadata.cachedCallbackKey as OnyxKey);
202199
});
203200
}
204201

lib/OnyxUtils.ts

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import type {
1414
ConnectOptions,
1515
DeepRecord,
1616
DefaultConnectCallback,
17-
DefaultConnectOptions,
1817
KeyValueMapping,
1918
CallbackToStateMapping,
2019
MultiMergeReplaceNullPatches,
@@ -580,31 +579,8 @@ function keysChanged<TKey extends CollectionKeyBase>(
580579
}
581580

582581
try {
583-
lastConnectionCallbackData.set(subscriber.subscriptionID, {
584-
value: cachedCollection,
585-
matchedKey: subscriber.key,
586-
});
587-
588-
if (subscriber.waitForCollectionCallback) {
589-
subscriber.callback(cachedCollection, subscriber.key);
590-
continue;
591-
}
592-
593-
// Not using waitForCollectionCallback — notify per changed key.
594-
// Re-check the subscription on each iteration because the callback may
595-
// synchronously disconnect itself (removing it from callbackToStateMapping),
596-
// in which case we must stop firing further callbacks for this subscriber.
597-
for (const dataKey of changedMemberKeys) {
598-
const currentSubscriber = callbackToStateMapping[subID];
599-
if (!currentSubscriber || typeof currentSubscriber.callback !== 'function') {
600-
break;
601-
}
602-
if (cachedCollection[dataKey] === previousCollection[dataKey]) {
603-
continue;
604-
}
605-
const currentSubscriberCallback = currentSubscriber.callback as DefaultConnectCallback<TKey>;
606-
currentSubscriberCallback(cachedCollection[dataKey], dataKey as TKey);
607-
}
582+
lastConnectionCallbackData.set(subscriber.subscriptionID, {value: cachedCollection, matchedKey: subscriber.key});
583+
subscriber.callback(cachedCollection, subscriber.key);
608584
} catch (error) {
609585
Logger.logAlert(`[OnyxUtils.keysChanged] Subscriber callback threw an error for key '${collectionKey}': ${error}`);
610586
}
@@ -685,9 +661,9 @@ function keyChanged<TKey extends OnyxKey>(
685661
continue;
686662
}
687663

688-
if (OnyxKeys.isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
689-
// Skip individual key changes for collection callbacks during collection updates
690-
// to prevent duplicate callbacks - the collection update will handle this properly
664+
if (OnyxKeys.isCollectionKey(subscriber.key)) {
665+
// Skip individual key changes during collection updates to prevent duplicate
666+
// callbacks - the collection update will handle this properly.
691667
if (isProcessingCollectionUpdate) {
692668
continue;
693669
}
@@ -733,10 +709,10 @@ function sendDataToConnection<TKey extends OnyxKey>(mapping: CallbackToStateMapp
733709
}
734710

735711
// Always read the latest value from cache to avoid stale or duplicate data.
736-
// For collection subscribers with waitForCollectionCallback, read the full collection.
712+
// For collection-root subscribers, read the full collection.
737713
// For individual key subscribers, read just that key's value.
738714
let value: OnyxValue<TKey> | undefined;
739-
if (OnyxKeys.isCollectionKey(mapping.key) && mapping.waitForCollectionCallback) {
715+
if (OnyxKeys.isCollectionKey(mapping.key)) {
740716
const collection = getCachedCollection(mapping.key);
741717
value = Object.keys(collection).length > 0 ? (collection as OnyxValue<TKey>) : undefined;
742718
} else {
@@ -754,7 +730,7 @@ function sendDataToConnection<TKey extends OnyxKey>(mapping: CallbackToStateMapp
754730
return;
755731
}
756732

757-
(mapping as DefaultConnectOptions<TKey>).callback?.(value, matchedKey as TKey);
733+
(mapping.callback as DefaultConnectCallback<TKey> | undefined)?.(value, matchedKey as TKey);
758734
}
759735

760736
/**
@@ -1175,30 +1151,19 @@ function subscribeToKey<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKe
11751151
cache.addNullishStorageKey(mapping.key);
11761152
}
11771153

1178-
const matchedKey = OnyxKeys.isCollectionKey(mapping.key) && mapping.waitForCollectionCallback ? mapping.key : undefined;
1154+
const matchedKey = OnyxKeys.isCollectionKey(mapping.key) ? mapping.key : undefined;
11791155

11801156
// Here we cannot use batching because the nullish value is expected to be set immediately for default props
11811157
// or they will be undefined.
11821158
sendDataToConnection(mapping, matchedKey);
11831159
return;
11841160
}
11851161

1186-
// When using a callback subscriber we will either trigger the provided callback for each key we find or combine all values
1187-
// into an object and just make a single call. The latter behavior is enabled by providing a waitForCollectionCallback key
1188-
// combined with a subscription to a collection key.
1162+
// When using a callback subscriber, a subscription to a collection key combines all matching
1163+
// member values into a single object and makes one call with the whole collection object.
11891164
if (typeof mapping.callback === 'function') {
11901165
if (OnyxKeys.isCollectionKey(mapping.key)) {
1191-
if (mapping.waitForCollectionCallback) {
1192-
getCollectionDataAndSendAsObject(matchingKeys, mapping);
1193-
return;
1194-
}
1195-
1196-
// We did not opt into using waitForCollectionCallback mode so the callback is called for every matching key.
1197-
multiGet(matchingKeys).then(() => {
1198-
for (const key of matchingKeys) {
1199-
sendDataToConnection(mapping, key as TKey);
1200-
}
1201-
});
1166+
getCollectionDataAndSendAsObject(matchingKeys, mapping);
12021167
return;
12031168
}
12041169

@@ -1458,7 +1423,7 @@ function multiSetWithRetry(data: OnyxMultiSetInput, retryAttempt?: number): Prom
14581423
// and subscriber state, matching the original per-key notification semantics.
14591424
cache.set(key, value);
14601425
// Skip subscriber notification on retry — already notified on attempt 0.
1461-
// waitForCollectionCallback subscribers re-fire on every keyChanged by contract.
1426+
// Collection-root subscribers re-fire on every keyChanged by contract.
14621427
if (!retryAttempt) {
14631428
keyChanged(key, value);
14641429
}
@@ -1549,7 +1514,7 @@ function setCollectionWithRetry<TKey extends CollectionKeyBase>({collectionKey,
15491514
for (const [key, value] of keyValuePairs) cache.set(key, value);
15501515

15511516
// Skip subscriber notification on retry — already notified on attempt 0.
1552-
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
1517+
// Collection-root subscribers re-fire on every keysChanged by contract.
15531518
if (!retryAttempt) {
15541519
keysChanged(collectionKey, mutableCollection, previousCollection);
15551520
}
@@ -1699,7 +1664,7 @@ function mergeCollectionWithPatches<TKey extends CollectionKeyBase>(
16991664
const previousCollection = getCachedCollection(collectionKey, existingKeys);
17001665
cache.merge(finalMergedCollection);
17011666
// Skip subscriber notification on retry — already notified on attempt 0.
1702-
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
1667+
// Collection-root subscribers re-fire on every keysChanged by contract.
17031668
if (!retryAttempt) {
17041669
keysChanged(collectionKey, finalMergedCollection, previousCollection);
17051670
}
@@ -1792,7 +1757,7 @@ function partialSetCollection<TKey extends CollectionKeyBase>({collectionKey, co
17921757
for (const [key, value] of keyValuePairs) cache.set(key, value);
17931758

17941759
// Skip subscriber notification on retry — already notified on attempt 0.
1795-
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
1760+
// Collection-root subscribers re-fire on every keysChanged by contract.
17961761
if (!retryAttempt) {
17971762
keysChanged(collectionKey, mutableCollection, previousCollection);
17981763
}

0 commit comments

Comments
 (0)