From 48274f28803821d1a2f3cf1a0ec17428bf684fdd Mon Sep 17 00:00:00 2001
From: Tomasz Misiukiewicz <tomasz.misiukiewicz@callstack.com>
Date: Wed, 10 Dec 2025 15:47:53 +0100
Subject: [PATCH 1/3] skip keyChanged notifications on collection update
 between tabs

---
 lib/Onyx.ts | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/lib/Onyx.ts b/lib/Onyx.ts
index eb79d42f7..38975afff 100644
--- a/lib/Onyx.ts
+++ b/lib/Onyx.ts
@@ -56,7 +56,19 @@ function init({
     if (shouldSyncMultipleInstances) {
         Storage.keepInstancesSync?.((key, value) => {
             cache.set(key, value);
-            OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>);
+
+            // Check if this is a collection member key to prevent duplicate callbacks
+            // When a collection is updated, individual members sync separately to other tabs
+            // Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
+            let isCollectionMember = false;
+            try {
+                OnyxUtils.getCollectionKey(key);
+                isCollectionMember = true;
+            } catch {
+                // Key is not a collection member
+            }
+
+            OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>, undefined, true, isCollectionMember);
         });
     }
 

From 6a166001cf6d6d7abfa6829d961cea5a0f41ff7e Mon Sep 17 00:00:00 2001
From: Tomasz Misiukiewicz <tomasz.misiukiewicz@callstack.com>
Date: Thu, 11 Dec 2025 08:07:07 +0100
Subject: [PATCH 2/3] create isCollectionMember util

---
 lib/Onyx.ts      | 10 ++--------
 lib/OnyxUtils.ts | 17 +++++++++++++++++
 2 files changed, 19 insertions(+), 8 deletions(-)

diff --git a/lib/Onyx.ts b/lib/Onyx.ts
index 38975afff..27751ae60 100644
--- a/lib/Onyx.ts
+++ b/lib/Onyx.ts
@@ -60,15 +60,9 @@ function init({
             // Check if this is a collection member key to prevent duplicate callbacks
             // When a collection is updated, individual members sync separately to other tabs
             // Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
-            let isCollectionMember = false;
-            try {
-                OnyxUtils.getCollectionKey(key);
-                isCollectionMember = true;
-            } catch {
-                // Key is not a collection member
-            }
+            const isKeyCollectionMember = OnyxUtils.isCollectionMember(key);
 
-            OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>, undefined, true, isCollectionMember);
+            OnyxUtils.keyChanged(key, value as OnyxValue<typeof key>, undefined, true, isKeyCollectionMember);
         });
     }
 
diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts
index 58168e02f..669eda867 100644
--- a/lib/OnyxUtils.ts
+++ b/lib/OnyxUtils.ts
@@ -478,6 +478,22 @@ function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collect
     return key.startsWith(collectionKey) && key.length > collectionKey.length;
 }
 
+/**
+ * Checks if a given key is a collection member key (not just a collection key).
+ * @param key - The key to check
+ * @returns true if the key is a collection member, false otherwise
+ */
+function isCollectionMember(key: OnyxKey): boolean {
+    try {
+        const collectionKey = getCollectionKey(key);
+        // If the key is longer than the collection key, it's a collection member
+        return key.length > collectionKey.length;
+    } catch (e) {
+        // If getCollectionKey throws, the key is not a collection member
+        return false;
+    }
+}
+
 /**
  * Splits a collection member key into the collection key part and the ID part.
  * @param key - The collection member key to split.
@@ -1694,6 +1710,7 @@ const OnyxUtils = {
     getCollectionKeys,
     isCollectionKey,
     isCollectionMemberKey,
+    isCollectionMember,
     splitCollectionMemberKey,
     isKeyMatch,
     tryGetCachedValue,

From 55fa8eacd1a52e2b6f5bcf33ce12eadcae91d98c Mon Sep 17 00:00:00 2001
From: Tomasz Misiukiewicz <tomasz.misiukiewicz@callstack.com>
Date: Thu, 11 Dec 2025 08:12:51 +0100
Subject: [PATCH 3/3] add unit tests for isCollectionMember

---
 tests/unit/onyxUtilsTest.ts | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/unit/onyxUtilsTest.ts b/tests/unit/onyxUtilsTest.ts
index b4f49e548..a390ed793 100644
--- a/tests/unit/onyxUtilsTest.ts
+++ b/tests/unit/onyxUtilsTest.ts
@@ -345,6 +345,36 @@ describe('OnyxUtils', () => {
         });
     });
 
+    describe('isCollectionMember', () => {
+        it('should return true for collection member keys', () => {
+            expect(OnyxUtils.isCollectionMember('test_123')).toBe(true);
+            expect(OnyxUtils.isCollectionMember('test_level_456')).toBe(true);
+            expect(OnyxUtils.isCollectionMember('test_level_last_789')).toBe(true);
+            expect(OnyxUtils.isCollectionMember('test_-1_something')).toBe(true);
+            expect(OnyxUtils.isCollectionMember('routes_abc')).toBe(true);
+        });
+
+        it('should return false for collection keys themselves', () => {
+            expect(OnyxUtils.isCollectionMember('test_')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('test_level_')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('test_level_last_')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('routes_')).toBe(false);
+        });
+
+        it('should return false for non-collection keys', () => {
+            expect(OnyxUtils.isCollectionMember('test')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('someRegularKey')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('notACollection')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('')).toBe(false);
+        });
+
+        it('should return false for invalid keys', () => {
+            expect(OnyxUtils.isCollectionMember('invalid_key_123')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('notregistered_')).toBe(false);
+            expect(OnyxUtils.isCollectionMember('notregistered_123')).toBe(false);
+        });
+    });
+
     describe('mergeChanges', () => {
         it("should return the last change if it's an array", () => {
             const {result} = OnyxUtils.mergeChanges([...testMergeChanges, [0, 1, 2]], testObject);
