From ebca04f341ecfc3e7be0104bf4c1f2290362052b Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Wed, 5 Aug 2026 10:05:48 +0200 Subject: [PATCH 1/3] Skip keyless update entries in updateSnapshots instead of crashing Onyx.update already skips entries without a string key (and clear/multiset entries legitimately have none), but passed the unfiltered array to updateSnapshots, which crashed on key.startsWith when a snapshot was cached. Fixes APP-6NT Co-Authored-By: Claude Fable 5 --- lib/OnyxUtils.ts | 6 ++++++ tests/unit/onyxTest.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 4dc1ba2ce..e23ff92eb 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -1211,6 +1211,12 @@ function updateSnapshots(data: Array>, me let updatedData: Record = {}; for (const {key, value} of data) { + // `clear`/`multiset` updates legitimately carry no key (and `Onyx.update` skips other keyless + // entries), so ignore them here too instead of crashing on `key.startsWith` below. + if (typeof key !== 'string') { + continue; + } + // snapshots are normal keys so we want to skip update if they are written to Onyx if (OnyxKeys.isCollectionMemberKey(snapshotCollectionKey, key)) { continue; diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 0021fcd8e..9f4cfe5b8 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1535,6 +1535,35 @@ describe('Onyx', () => { expect(callback.mock.calls[1][1]).toBe(ONYX_KEYS.COLLECTION.SNAPSHOT); }); + it('should skip update entries without a key when updating Snapshots instead of rejecting', async () => { + const cat = `${ONYX_KEYS.COLLECTION.ANIMALS}cat`; + const snapshot1 = `${ONYX_KEYS.COLLECTION.SNAPSHOT}1`; + + const initialValue = {name: 'Fluffy'}; + const finalValue = {name: 'Kitty'}; + + await Onyx.set(cat, initialValue); + await Onyx.set(snapshot1, {data: {[cat]: initialValue}}); + + const callback = jest.fn(); + + Onyx.connect({ + key: ONYX_KEYS.COLLECTION.SNAPSHOT, + callback, + }); + + await waitForPromisesToResolve(); + + // A keyless entry (e.g. a malformed server update) used to crash updateSnapshots with + // "can't access property 'startsWith', key is undefined" and reject the whole update. + const keylessUpdate = {onyxMethod: Onyx.METHOD.MERGE, value: {name: 'Ghost'}} as unknown as OnyxUpdate; + + await expect(Onyx.update([keylessUpdate, {key: cat, value: finalValue, onyxMethod: Onyx.METHOD.MERGE}])).resolves.not.toThrow(); + + // The valid update still lands in the snapshot. + expect(callback.mock.calls.at(-1)?.[0]).toEqual({[snapshot1]: {data: {[cat]: finalValue}}}); + }); + describe('update', () => { let logInfoFn = jest.fn(); From 81627b06ee8473977759cabc84c790a1d3ab907f Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Mon, 10 Aug 2026 15:16:38 +0200 Subject: [PATCH 2/3] Remove review-flagged comments --- lib/OnyxUtils.ts | 2 -- tests/unit/onyxTest.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index e23ff92eb..a5fbf7e1d 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -1211,8 +1211,6 @@ function updateSnapshots(data: Array>, me let updatedData: Record = {}; for (const {key, value} of data) { - // `clear`/`multiset` updates legitimately carry no key (and `Onyx.update` skips other keyless - // entries), so ignore them here too instead of crashing on `key.startsWith` below. if (typeof key !== 'string') { continue; } diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 9f4cfe5b8..a606e6a83 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1554,8 +1554,6 @@ describe('Onyx', () => { await waitForPromisesToResolve(); - // A keyless entry (e.g. a malformed server update) used to crash updateSnapshots with - // "can't access property 'startsWith', key is undefined" and reject the whole update. const keylessUpdate = {onyxMethod: Onyx.METHOD.MERGE, value: {name: 'Ghost'}} as unknown as OnyxUpdate; await expect(Onyx.update([keylessUpdate, {key: cat, value: finalValue, onyxMethod: Onyx.METHOD.MERGE}])).resolves.not.toThrow(); From 19dade93a3dc9b78310543575ea5eb19d140e3d9 Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Tue, 11 Aug 2026 14:40:11 +0200 Subject: [PATCH 3/3] Log skipped keyless update entries in updateSnapshots --- lib/OnyxUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index a5fbf7e1d..6cbd61d7d 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -1212,6 +1212,7 @@ function updateSnapshots(data: Array>, me for (const {key, value} of data) { if (typeof key !== 'string') { + Logger.logInfo(`Invalid ${typeof key} key provided in Onyx update. Key must be of type string. Skipping snapshot update for this entry.`); continue; }