Skip to content

[$250] Collapse Onyx collection-key subscriptions to a single whole-collection delivery mode and remove the waitForCollectionCallback option #94331

Description

@mountiny

Coming from here

Background

A component reads an Onyx collection — the set of records sharing a key prefix, e.g. every report under report_ — through either useOnyx or the imperative Onyx.connect/Onyx.connectWithoutView. Such a subscription delivers updates in one of two modes:

Per-member (the default): the callback fires once per member — on the initial connection, once for each member already present in the collection; on each later write, once for every member whose value changed — as callback(memberValue, memberKey).
Whole-collection (waitForCollectionCallback: true): the callback fires a single time with the entire collection object — callback(collection, collectionKey).

Both modes are backed by the same data: Onyx keeps a frozen object for each collection in its cache, in which members that didn't change retain the same reference across writes. Across all of expensify-app, only three consumers still use per-member mode (Report/index.ts, replaceOptimisticReportWithActualReport.ts, Transaction.ts))); every other collection subscriber uses whole-collection mode.

Problem

When a collection subscription can deliver its data two ways — per-member (the default) or as the whole collection — if an engineer writes, reviews, or changes a collection subscription or the notify code behind it, then they must understand and keep both firing behaviors working even though the two carry the same data. Only three consumers use per-member mode, and each just rebuilds the whole collection it withholds — one of them incorrectly — so the mode adds permanent cost while giving its users nothing whole-collection mode doesn't.

Solution

Remove the waitForCollectionCallback option and the per-member dispatch path, leaving a single delivery mode: a collection subscription always hands back the whole collection object in one callback. This collapses the two-mode choice every collection subscription had to make into one and deletes the dedicated per-member branch from the notification path, so neither new subscriptions nor future changes to the notify code have to reason about it.

Nothing is lost. The collection object already carries every member, so a consumer that needs to know which members changed compares the new object against the previous one — members that didn't change are reference-equal, so the changed set falls out in O(members), with an O(1) check per member. Because per-member mode delivered no information the whole-collection object lacks, it is a strict subset and can be removed outright rather than preserved.

All the three remaining per-member consumers maintain a module-level cache of an entire collection, rebuilt from the per-member callback — exactly what whole-collection mode delivers in one call. Each switches to whole-collection mode and reads the collection object directly:

  • Report/index.ts and replaceOptimisticReportWithActualReport.ts subscribe to COLLECTION.REPORT_ACTIONS and accumulate allReportActions[reportID] = actions per member. Both become a single assignment from the collection object, which is already keyed by member (report) ID.
  • Transaction.ts subscribes to COLLECTION.TRANSACTION_VIOLATIONS and assigns each fired member into a flat variable (allTransactionViolations = val ?? []), so only the last member to fire survived — a latent bug. Reading the whole collection object instead fixes it.

A consumer that truly needed per-member granularity could recover it by diffing the new collection object against the previous one (reference-equal members fall out in O(members), O(1) each), but none of these three do — they all want the whole collection.

Issue OwnerCurrent Issue Owner: @fabioh8010
Upwork Automation - Do Not Edit

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorReviewingHas a PR in review

Type

No type
No fields configured for issues without a type.

Projects

Status
CRITICAL

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions