From 3a5c7c53c6366b719dac29fa4fa2bebdc80fe00a Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 11 Mar 2025 15:43:29 +0100 Subject: [PATCH 1/9] expose what change triggered callback --- lib/OnyxConnectionManager.ts | 11 ++++++++--- lib/OnyxUtils.ts | 10 +++++----- lib/types.ts | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/OnyxConnectionManager.ts b/lib/OnyxConnectionManager.ts index bf50a487d..cbed198f7 100644 --- a/lib/OnyxConnectionManager.ts +++ b/lib/OnyxConnectionManager.ts @@ -42,6 +42,11 @@ type ConnectionMetadata = { * The last callback key returned by `OnyxUtils.subscribeToKey()`'s callback. */ cachedCallbackKey?: OnyxKey; + + /** + * The value that triggered the last update + */ + sourceValue?: OnyxValue; }; /** @@ -135,7 +140,7 @@ class OnyxConnectionManager { const connection = this.connectionsMap.get(connectionID); connection?.callbacks.forEach((callback) => { - callback(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey); + callback(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey, connection.sourceValue); }); } @@ -159,7 +164,7 @@ class OnyxConnectionManager { // If the subscriber is a `withOnyx` HOC we don't define `callback` as the HOC will use // its own logic to handle the data. if (!utils.hasWithOnyxInstance(connectOptions)) { - callback = (value, key) => { + callback = (value, key, sourceValue) => { const createdConnection = this.connectionsMap.get(connectionID); if (createdConnection) { // We signal that the first connection was made and now any new subscribers @@ -167,7 +172,7 @@ class OnyxConnectionManager { createdConnection.isConnectionMade = true; createdConnection.cachedCallbackValue = value; createdConnection.cachedCallbackKey = key; - + createdConnection.sourceValue = sourceValue; this.fireCallbacks(connectionID); } }; diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index de1c4eada..b66a542d2 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -695,7 +695,7 @@ function keysChanged( // send the whole cached collection. if (isSubscribedToCollectionKey) { if (subscriber.waitForCollectionCallback) { - subscriber.callback(cachedCollection, subscriber.key); + subscriber.callback(cachedCollection, subscriber.key, partialCollection); continue; } @@ -709,7 +709,7 @@ function keysChanged( continue; } - subscriber.callback(cachedCollection[dataKey], dataKey); + subscriber.callback(cachedCollection[dataKey], dataKey, partialCollection); } continue; } @@ -722,7 +722,7 @@ function keysChanged( } const subscriberCallback = subscriber.callback as DefaultConnectCallback; - subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey); + subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey, partialCollection); continue; } @@ -905,12 +905,12 @@ function keyChanged( } cachedCollection[key] = value; - subscriber.callback(cachedCollection, subscriber.key); + subscriber.callback(cachedCollection, subscriber.key, value); continue; } const subscriberCallback = subscriber.callback as DefaultConnectCallback; - subscriberCallback(value, key); + subscriberCallback(value, key, value); lastConnectionCallbackData.set(subscriber.subscriptionID, value); continue; diff --git a/lib/types.ts b/lib/types.ts index b722fa41f..8ab2f5d6a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -275,10 +275,10 @@ type BaseConnectOptions = { }; /** Represents the callback function used in `Onyx.connect()` method with a regular key. */ -type DefaultConnectCallback = (value: OnyxEntry, key: TKey) => void; +type DefaultConnectCallback = (value: OnyxEntry, key: TKey, sourceValue?: OnyxEntry) => void; /** Represents the callback function used in `Onyx.connect()` method with a collection key. */ -type CollectionConnectCallback = (value: NonUndefined>, key: TKey) => void; +type CollectionConnectCallback = (value: NonUndefined>, key: TKey, sourceValue?: OnyxEntry) => void; /** Represents the options used in `Onyx.connect()` method with a regular key. */ // NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method! From 8d3982f69eae3e7e7c90b8bddaf394bd82a6b001 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 12 Mar 2025 08:40:40 +0100 Subject: [PATCH 2/9] expose value only when waitForCollectionCallback is true --- lib/OnyxUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index b66a542d2..84086326a 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -709,7 +709,7 @@ function keysChanged( continue; } - subscriber.callback(cachedCollection[dataKey], dataKey, partialCollection); + subscriber.callback(cachedCollection[dataKey], dataKey); } continue; } @@ -722,7 +722,7 @@ function keysChanged( } const subscriberCallback = subscriber.callback as DefaultConnectCallback; - subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey, partialCollection); + subscriberCallback(cachedCollection[subscriber.key], subscriber.key as TKey); continue; } @@ -910,7 +910,7 @@ function keyChanged( } const subscriberCallback = subscriber.callback as DefaultConnectCallback; - subscriberCallback(value, key, value); + subscriberCallback(value, key); lastConnectionCallbackData.set(subscriber.subscriptionID, value); continue; From d8f288ff0987eea345946728f439ba1bc4711960 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 12 Mar 2025 12:51:59 +0100 Subject: [PATCH 3/9] update types --- lib/OnyxConnectionManager.ts | 19 ++++++++++++++----- lib/types.ts | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/OnyxConnectionManager.ts b/lib/OnyxConnectionManager.ts index cbed198f7..824a36c41 100644 --- a/lib/OnyxConnectionManager.ts +++ b/lib/OnyxConnectionManager.ts @@ -3,10 +3,10 @@ import * as Logger from './Logger'; import type {ConnectOptions} from './Onyx'; import OnyxUtils from './OnyxUtils'; import * as Str from './Str'; -import type {DefaultConnectCallback, DefaultConnectOptions, OnyxKey, OnyxValue} from './types'; +import type {CollectionConnectCallback, DefaultConnectCallback, DefaultConnectOptions, OnyxKey, OnyxValue} from './types'; import utils from './utils'; -type ConnectCallback = DefaultConnectCallback; +type ConnectCallback = DefaultConnectCallback | CollectionConnectCallback; /** * Represents the connection's metadata that contains the necessary properties @@ -47,6 +47,11 @@ type ConnectionMetadata = { * The value that triggered the last update */ sourceValue?: OnyxValue; + + /** + * Whether the subscriber is waiting for the collection callback to be fired. + */ + waitForCollectionCallback?: boolean; }; /** @@ -140,7 +145,11 @@ class OnyxConnectionManager { const connection = this.connectionsMap.get(connectionID); connection?.callbacks.forEach((callback) => { - callback(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey, connection.sourceValue); + if (connection.waitForCollectionCallback) { + (callback as CollectionConnectCallback)(connection.cachedCallbackValue as Record, connection.cachedCallbackKey as OnyxKey, connection.sourceValue); + } else { + (callback as DefaultConnectCallback)(connection.cachedCallbackValue, connection.cachedCallbackKey as OnyxKey); + } }); } @@ -179,8 +188,8 @@ class OnyxConnectionManager { } subscriptionID = OnyxUtils.subscribeToKey({ - ...(connectOptions as DefaultConnectOptions), - callback, + ...connectOptions, + callback: callback as DefaultConnectCallback, }); connectionMetadata = { diff --git a/lib/types.ts b/lib/types.ts index 8ab2f5d6a..2c9841292 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -275,7 +275,7 @@ type BaseConnectOptions = { }; /** Represents the callback function used in `Onyx.connect()` method with a regular key. */ -type DefaultConnectCallback = (value: OnyxEntry, key: TKey, sourceValue?: OnyxEntry) => void; +type DefaultConnectCallback = (value: OnyxEntry, key: TKey) => void; /** Represents the callback function used in `Onyx.connect()` method with a collection key. */ type CollectionConnectCallback = (value: NonUndefined>, key: TKey, sourceValue?: OnyxEntry) => void; From cc1f196f9c9c9c41da579886a6926b6ab3722a29 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 12 Mar 2025 13:42:28 +0100 Subject: [PATCH 4/9] fix waitForConnectionCallback --- lib/OnyxConnectionManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/OnyxConnectionManager.ts b/lib/OnyxConnectionManager.ts index 824a36c41..1835df8f5 100644 --- a/lib/OnyxConnectionManager.ts +++ b/lib/OnyxConnectionManager.ts @@ -197,6 +197,7 @@ class OnyxConnectionManager { onyxKey: connectOptions.key, isConnectionMade: false, callbacks: new Map(), + waitForCollectionCallback: connectOptions.waitForCollectionCallback, }; this.connectionsMap.set(connectionID, connectionMetadata); From 5c827041b42cf99d79417a82b16f5e02be680e93 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 12 Mar 2025 14:17:37 +0100 Subject: [PATCH 5/9] fix tests --- tests/unit/OnyxConnectionManagerTest.ts | 2 +- tests/unit/onyxClearWebStorageTest.ts | 15 +++++++++++-- tests/unit/onyxTest.ts | 29 ++++++++++++++++--------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/tests/unit/OnyxConnectionManagerTest.ts b/tests/unit/OnyxConnectionManagerTest.ts index 91ab0da0f..13dd9548c 100644 --- a/tests/unit/OnyxConnectionManagerTest.ts +++ b/tests/unit/OnyxConnectionManagerTest.ts @@ -164,7 +164,7 @@ describe('OnyxConnectionManager', () => { expect(callback1).toHaveBeenNthCalledWith(2, obj2, `${ONYXKEYS.COLLECTION.TEST_KEY}entry2`); expect(callback2).toHaveBeenCalledTimes(1); - expect(callback2).toHaveBeenCalledWith(collection, undefined); + expect(callback2).toHaveBeenCalledWith(collection, undefined, undefined); connectionManager.disconnect(connection1); connectionManager.disconnect(connection2); diff --git a/tests/unit/onyxClearWebStorageTest.ts b/tests/unit/onyxClearWebStorageTest.ts index d2894db69..3e6388364 100644 --- a/tests/unit/onyxClearWebStorageTest.ts +++ b/tests/unit/onyxClearWebStorageTest.ts @@ -161,7 +161,7 @@ describe('Set data while storage is clearing', () => { expect(collectionCallback).toHaveBeenCalledTimes(3); // And it should be called with the expected parameters each time - expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined); + expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); expect(collectionCallback).toHaveBeenNthCalledWith( 2, { @@ -171,8 +171,19 @@ describe('Set data while storage is clearing', () => { test_4: 4, }, ONYX_KEYS.COLLECTION.TEST, + { + test_1: 1, + test_2: 2, + test_3: 3, + test_4: 4, + }, ); - expect(collectionCallback).toHaveBeenLastCalledWith({}, ONYX_KEYS.COLLECTION.TEST); + expect(collectionCallback).toHaveBeenLastCalledWith({}, ONYX_KEYS.COLLECTION.TEST, { + test_1: undefined, + test_2: undefined, + test_3: undefined, + test_4: undefined, + }); }) ); }); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 44400e715..53f6334c3 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -989,7 +989,7 @@ describe('Onyx', () => { .then(() => { // Then we expect the callback to be called only once and the initial stored value to be initialCollectionData expect(mockCallback).toHaveBeenCalledTimes(1); - expect(mockCallback).toHaveBeenCalledWith(initialCollectionData, undefined); + expect(mockCallback).toHaveBeenCalledWith(initialCollectionData, undefined, undefined); }); }); @@ -1015,10 +1015,10 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(2); // AND the value for the first call should be null since the collection was not initialized at that point - expect(mockCallback).toHaveBeenNthCalledWith(1, undefined, undefined); + expect(mockCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); // AND the value for the second call should be collectionUpdate since the collection was updated - expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY); + expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate); }) ); }); @@ -1073,7 +1073,8 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(2); // AND the value for the second call should be collectionUpdate - expect(mockCallback).toHaveBeenLastCalledWith(collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY); + expect(mockCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); + expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); }) ); }); @@ -1108,7 +1109,7 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(2); // And the value for the second call should be collectionUpdate - expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY); + expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); }) // When merge is called again with the same collection not modified @@ -1149,7 +1150,7 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(1); // And the value for the second call should be collectionUpdate - expect(mockCallback).toHaveBeenNthCalledWith(1, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY); + expect(mockCallback).toHaveBeenNthCalledWith(1, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); }) // When merge is called again with the same collection not modified @@ -1186,8 +1187,8 @@ describe('Onyx', () => { {onyxMethod: Onyx.METHOD.MERGE_COLLECTION, key: ONYX_KEYS.COLLECTION.TEST_UPDATE, value: {[itemKey]: {a: 'a'}}}, ]).then(() => { expect(collectionCallback).toHaveBeenCalledTimes(2); - expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined); - expect(collectionCallback).toHaveBeenNthCalledWith(2, {[itemKey]: {a: 'a'}}, ONYX_KEYS.COLLECTION.TEST_UPDATE); + expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); + expect(collectionCallback).toHaveBeenNthCalledWith(2, {[itemKey]: {a: 'a'}}, ONYX_KEYS.COLLECTION.TEST_UPDATE, {a: 'a'}); expect(testCallback).toHaveBeenCalledTimes(2); expect(testCallback).toHaveBeenNthCalledWith(1, undefined, undefined); @@ -1426,7 +1427,9 @@ describe('Onyx', () => { }) .then(() => { expect(collectionCallback).toHaveBeenCalledTimes(3); - expect(collectionCallback).toHaveBeenCalledWith(collectionDiff, ONYX_KEYS.COLLECTION.ANIMALS); + expect(collectionCallback).toHaveBeenNthCalledWith(1, {[cat]: initialValue}, ONYX_KEYS.COLLECTION.ANIMALS, initialValue); + expect(collectionCallback).toHaveBeenNthCalledWith(2, {[cat]: initialValue}, undefined, undefined); + expect(collectionCallback).toHaveBeenNthCalledWith(3, collectionDiff, ONYX_KEYS.COLLECTION.ANIMALS, {[cat]: initialValue, [dog]: {name: 'Rex'}}); // Cat hasn't changed from its original value, expect only the initial connect callback expect(catCallback).toHaveBeenCalledTimes(1); @@ -1558,6 +1561,10 @@ describe('Onyx', () => { }, }, ONYX_KEYS.COLLECTION.ROUTES, + { + [holidayRoute]: {waypoints: {0: 'Bed', 1: 'Home', 2: 'Beach', 3: 'Restaurant', 4: 'Home'}}, + [routineRoute]: {waypoints: {0: 'Bed', 1: 'Home', 2: 'Work', 3: 'Gym'}}, + }, ); connections.map((id) => Onyx.disconnect(id)); @@ -1628,6 +1635,7 @@ describe('Onyx', () => { [cat]: {age: 3, sound: 'meow'}, }, ONYX_KEYS.COLLECTION.ANIMALS, + {age: 3, sound: 'meow'}, ); expect(animalsCollectionCallback).toHaveBeenNthCalledWith( 2, @@ -1636,6 +1644,7 @@ describe('Onyx', () => { [dog]: {size: 'M', sound: 'woof'}, }, ONYX_KEYS.COLLECTION.ANIMALS, + {[dog]: {size: 'M', sound: 'woof'}}, ); expect(catCallback).toHaveBeenNthCalledWith(1, {age: 3, sound: 'meow'}, cat); @@ -1647,6 +1656,7 @@ describe('Onyx', () => { [lisa]: {age: 21, car: 'SUV'}, }, ONYX_KEYS.COLLECTION.PEOPLE, + {[bob]: {age: 25, car: 'sedan'}, [lisa]: {age: 21, car: 'SUV'}}, ); connections.map((id) => Onyx.disconnect(id)); @@ -1741,7 +1751,6 @@ describe('Onyx', () => { callback: (value) => (result = value), waitForCollectionCallback: true, }); - // Set initial collection state await Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ROUTES, { [routeA]: {name: 'Route A'}, From 83e565b13413cc0b8a524b5159b2cd4cf24d0998 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 12 Mar 2025 18:20:46 +0100 Subject: [PATCH 6/9] unify return type of source value --- lib/OnyxUtils.ts | 2 +- lib/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 84086326a..5ec04ecb4 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -905,7 +905,7 @@ function keyChanged( } cachedCollection[key] = value; - subscriber.callback(cachedCollection, subscriber.key, value); + subscriber.callback(cachedCollection, subscriber.key, {[key]: value}); continue; } diff --git a/lib/types.ts b/lib/types.ts index 2c9841292..c492d869a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -278,7 +278,7 @@ type BaseConnectOptions = { type DefaultConnectCallback = (value: OnyxEntry, key: TKey) => void; /** Represents the callback function used in `Onyx.connect()` method with a collection key. */ -type CollectionConnectCallback = (value: NonUndefined>, key: TKey, sourceValue?: OnyxEntry) => void; +type CollectionConnectCallback = (value: NonUndefined>, key: TKey, sourceValue?: OnyxValue) => void; /** Represents the options used in `Onyx.connect()` method with a regular key. */ // NOTE: Any changes to this type like adding or removing options must be accounted in OnyxConnectionManager's `generateConnectionID()` method! From 2aebe4d672d34e567c6f7bce8ebd5d06cda298e1 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 13 Mar 2025 08:43:10 +0100 Subject: [PATCH 7/9] update tests --- tests/unit/onyxTest.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 53f6334c3..7de606dcf 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1074,7 +1074,9 @@ describe('Onyx', () => { // AND the value for the second call should be collectionUpdate expect(mockCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); - expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); + expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, { + [`${ONYX_KEYS.COLLECTION.TEST_POLICY}1`]: collectionUpdate.testPolicy_1, + }); }) ); }); @@ -1109,7 +1111,7 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(2); // And the value for the second call should be collectionUpdate - expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); + expect(mockCallback).toHaveBeenNthCalledWith(2, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, {testPolicy_1: collectionUpdate.testPolicy_1}); }) // When merge is called again with the same collection not modified @@ -1150,7 +1152,7 @@ describe('Onyx', () => { expect(mockCallback).toHaveBeenCalledTimes(1); // And the value for the second call should be collectionUpdate - expect(mockCallback).toHaveBeenNthCalledWith(1, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, collectionUpdate.testPolicy_1); + expect(mockCallback).toHaveBeenNthCalledWith(1, collectionUpdate, ONYX_KEYS.COLLECTION.TEST_POLICY, {testPolicy_1: collectionUpdate.testPolicy_1}); }) // When merge is called again with the same collection not modified @@ -1188,7 +1190,7 @@ describe('Onyx', () => { ]).then(() => { expect(collectionCallback).toHaveBeenCalledTimes(2); expect(collectionCallback).toHaveBeenNthCalledWith(1, undefined, undefined, undefined); - expect(collectionCallback).toHaveBeenNthCalledWith(2, {[itemKey]: {a: 'a'}}, ONYX_KEYS.COLLECTION.TEST_UPDATE, {a: 'a'}); + expect(collectionCallback).toHaveBeenNthCalledWith(2, {[itemKey]: {a: 'a'}}, ONYX_KEYS.COLLECTION.TEST_UPDATE, {[itemKey]: {a: 'a'}}); expect(testCallback).toHaveBeenCalledTimes(2); expect(testCallback).toHaveBeenNthCalledWith(1, undefined, undefined); @@ -1427,7 +1429,7 @@ describe('Onyx', () => { }) .then(() => { expect(collectionCallback).toHaveBeenCalledTimes(3); - expect(collectionCallback).toHaveBeenNthCalledWith(1, {[cat]: initialValue}, ONYX_KEYS.COLLECTION.ANIMALS, initialValue); + expect(collectionCallback).toHaveBeenNthCalledWith(1, {[cat]: initialValue}, ONYX_KEYS.COLLECTION.ANIMALS, {[cat]: initialValue}); expect(collectionCallback).toHaveBeenNthCalledWith(2, {[cat]: initialValue}, undefined, undefined); expect(collectionCallback).toHaveBeenNthCalledWith(3, collectionDiff, ONYX_KEYS.COLLECTION.ANIMALS, {[cat]: initialValue, [dog]: {name: 'Rex'}}); @@ -1635,7 +1637,7 @@ describe('Onyx', () => { [cat]: {age: 3, sound: 'meow'}, }, ONYX_KEYS.COLLECTION.ANIMALS, - {age: 3, sound: 'meow'}, + {[cat]: {age: 3, sound: 'meow'}}, ); expect(animalsCollectionCallback).toHaveBeenNthCalledWith( 2, From 2be6ec187a39c97a3d49683f92969b15e8448f95 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 13 Mar 2025 08:54:47 +0100 Subject: [PATCH 8/9] add tests --- tests/unit/OnyxConnectionManagerTest.ts | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/unit/OnyxConnectionManagerTest.ts b/tests/unit/OnyxConnectionManagerTest.ts index 13dd9548c..7715b3beb 100644 --- a/tests/unit/OnyxConnectionManagerTest.ts +++ b/tests/unit/OnyxConnectionManagerTest.ts @@ -544,4 +544,71 @@ describe('OnyxConnectionManager', () => { }).not.toThrow(); }); }); + + describe('sourceValue parameter', () => { + it('should pass the sourceValue parameter to collection callbacks when waitForCollectionCallback is true', async () => { + const obj1 = {id: 'entry1_id', name: 'entry1_name'}; + const obj2 = {id: 'entry2_id', name: 'entry2_name'}; + + const callback = jest.fn(); + const connection = connectionManager.connect({ + key: ONYXKEYS.COLLECTION.TEST_KEY, + callback, + waitForCollectionCallback: true, + }); + + await act(async () => waitForPromisesToResolve()); + + // Initial callback with undefined values + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith(undefined, undefined, undefined); + + // Reset mock to test the next update + callback.mockReset(); + + // Update with first object + await Onyx.merge(`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`, obj1); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith({[`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`]: obj1}, ONYXKEYS.COLLECTION.TEST_KEY, {[`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`]: obj1}); + + // Reset mock to test the next update + callback.mockReset(); + + // Update with second object + await Onyx.merge(`${ONYXKEYS.COLLECTION.TEST_KEY}entry2`, obj2); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith( + { + [`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`]: obj1, + [`${ONYXKEYS.COLLECTION.TEST_KEY}entry2`]: obj2, + }, + ONYXKEYS.COLLECTION.TEST_KEY, + {[`${ONYXKEYS.COLLECTION.TEST_KEY}entry2`]: obj2}, + ); + + connectionManager.disconnect(connection); + }); + + it('should not pass sourceValue to regular callbacks when waitForCollectionCallback is false', async () => { + const obj1 = {id: 'entry1_id', name: 'entry1_name'}; + + const callback = jest.fn(); + const connection = connectionManager.connect({ + key: ONYXKEYS.COLLECTION.TEST_KEY, + callback, + waitForCollectionCallback: false, + }); + + await act(async () => waitForPromisesToResolve()); + + // Update with object + await Onyx.merge(`${ONYXKEYS.COLLECTION.TEST_KEY}entry1`, obj1); + + expect(callback).toHaveBeenCalledWith(obj1, `${ONYXKEYS.COLLECTION.TEST_KEY}entry1`); + + connectionManager.disconnect(connection); + }); + }); }); From 61f8e89f8d03c412fe353d8714b4917e54f4a6ed Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Thu, 13 Mar 2025 08:57:37 +0100 Subject: [PATCH 9/9] update README --- README.md | 4 ++-- tests/unit/onyxTest.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f82919919..7eaeb2783 100644 --- a/README.md +++ b/README.md @@ -316,11 +316,11 @@ This will fire the callback once per member key depending on how many collection Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, - callback: (allReports) => {...}, + callback: (allReports, collectionKey, sourceValue) => {...}, }); ``` -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. +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. The `sourceValue` parameter contains only the specific keys and values that triggered the current update, which can be useful for optimizing your code to only process what changed. This parameter is not available when `waitForCollectionCallback` is false or the key is not a collection key. ### Performance Considerations When Using Collections diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 7de606dcf..a42737d5e 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1753,6 +1753,7 @@ describe('Onyx', () => { callback: (value) => (result = value), waitForCollectionCallback: true, }); + // Set initial collection state await Onyx.mergeCollection(ONYX_KEYS.COLLECTION.ROUTES, { [routeA]: {name: 'Route A'},