Remove waitForCollectionCallback from Onyx#799
Conversation
Collection-root subscriptions (via Onyx.connect or useOnyx) now always deliver the whole collection snapshot to the callback. The legacy per-member delivery mode for collection-root subscribers is removed; consumers needing per-member processing subscribe to a single member key. Collapses the DefaultConnectOptions/CollectionConnectOptions discriminated union into one ConnectOptions shape (callback typed on TKey), simplifies generateConnectionID and the keyChanged/keysChanged dispatch, and drops the option from useOnyx's internal connect. Per-member-mode tests were migrated to assert snapshot delivery rather than deleted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01704211bf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (OnyxKeys.isCollectionKey(subscriber.key)) { | ||
| // Collection-root subscribers always receive the whole collection snapshot. | ||
| // Skip individual key changes during collection updates to prevent duplicate | ||
| // callbacks - the collection update will handle this properly. | ||
| if (isProcessingCollectionUpdate) { |
There was a problem hiding this comment.
Don't skip synced collection-member updates
On web with multiple tabs, InstanceSync raises storage events per member key and Onyx.ts calls keyChanged(..., isKeyCollectionMember) with true for those collection-member updates. With this broadened collection-root branch, the receiving tab hits the isProcessingCollectionUpdate continue, and no keysChanged() runs afterward in that tab, so Onyx.connect({key: COLLECTION...}) subscribers never receive the required collection snapshot when another tab changes the collection. Please allow the storage-sync path to notify collection-root subscribers instead of treating it like a local batched collection update.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 9263ec9. Basically this flow was already flawed in main – the collection key callbacks with waitForCollectionCallback: true were not notified during tab syncs, only collection key callbacks without waitForCollectionCallback: true (per-member mode) or regular/collection member keys.
The solution here is to batch the writes across tab syncs instead of treating each synced member as an independent update. InstanceSync now raises a single SYNC_ONYX event carrying the JSON array of all keys changed in one write (instead of one event per key), and the receiving tab reads them with a single multiGet and hands Onyx the whole batch via a new batched onStorageKeysChanged(pairs) callback (with a single-key fallback for backwards compatibility).
Onyx then processes that batch synchronously — grouping members by their parent collection and firing exactly one keysChanged() per affected collection (plus keyChanged() for individual keys) — which mirrors how a local mergeCollection works. This makes a cross-tab collection update notify each collection-root subscriber (Onyx.connect/useOnyx on a collection key) once, in O(N), rather than re-delivering the whole collection once per member (O(N^2), which can crash the receiving tab depending on the payload) or never delivering it at all.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9263ec964d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3da566e848
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
I caught one bug with the tab sync logic when clearing Onyx cache, investigating it. |
47c46ba to
1d77980
Compare
|
Regarding #799 (comment), I decided to extract the whole tab sync fixes into a separate PR (that will need a separate bug report issue), because:
|
I agree with this. @mountiny, could you create a separate issue for it? |
|
@fabioh8010 is creating one |
|
@fabioh8010 issue was created can you link it here please @truph01 can you continue with the review? |
|
Onyx and E/App PRs updated and ready to review @truph01 |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeScreen.Recording.2026-06-30.at.18.03.11.moviOS: HybridAppiOS: mWeb SafariScreen.Recording.2026-06-30.at.18.01.13.movMacOS: Chrome / SafariScreen.Recording.2026-06-30.at.17.59.23.mov |
Details
Removes
waitForCollectionCallback— the per-member collection subscription mode — so a collection-key subscription always receives the whole collection. Removing it exposed a latent multi-tab sync bug, which this PR also fixes.Cross-tab sync fix
Now that every collection subscriber is a whole-collection subscriber, multi-instance (web, multi-tab) sync needed fixing.
InstanceSyncraised onelocalStorageevent per changed key and the receiving tab read each with its own asyncgetItem, so amergeCollectionof N members arrived as N independent events across N tasks. That meant collection-root subscribers either received nothing cross-tab, or — if notified per member — had the whole collection re-delivered N times (O(N²)), crashing the receiving tab on large collections.The fix preserves the write's batch boundary across tabs:
InstanceSyncraises a singleSYNC_ONYXevent carrying the JSON array of all keys changed in a write (falls back to a single key for the previous format).multiGetand hands the whole batch to Onyx via a new batchedonStorageKeysChanged(pairs)callback.keysChanged()per collection (pluskeyChanged()for individual keys) — mirroring a localmergeCollection.Result: a cross-tab collection update notifies each collection-root subscriber once (O(N)) instead of N times or never.
Related Issues
Expensify/App#94331
Expensify/App#94839
Linked E/App PR
Expensify/App#93436
Automated Tests
Tests were changed to accomodate the changes.
Manual Tests
We are basically just removing
waitForCollectionCallbackusage here, functionality should stay the same so we only need to test the two files where we actually did some meaningful change (Report/index.tsandreplaceOptimisticReportWithActualReport.ts).Author Checklist
### Related Issuessection above### Linked E/App PRsection above, and verified this change against it (E/App CI passed and manual testing completed)TestssectiontoggleReportand notonIconClick)myBool && <MyComponent />.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Avataris modified, I verified thatAvataris working as expected in all cases)mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
Screen.Recording.2026-06-30.at.08.46.47.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-06-30.at.08.40.31.mov