Skip to content

[Due for payment 2026-08-05] Remove sourceValue from Onyx #94348

Description

@mountiny

Coming from here

Background

A collection callback receives an optional third argument, sourceValue (mirrored as a field on useOnyx's result metadata): the specific member(s) the triggering write touched, so a consumer can process just the delta instead of scanning the whole collection. Like per-member delivery, it predates the reference-stable frozen collection objects now kept in OnyxCache, in which members that didn't change keep the same reference across writes.

Problem

When a consumer reads sourceValue to learn which collection members a write changed, if several updates arrive close enough that React batches the resulting re-renders into one, then the hook keeps only the last write's sourceValue — it overwrites a single ref per update and the render reads only its final value — so a consumer using it to invalidate a derived cache silently misses every batched-away member and leaves that cache stale. The ref is also never cleared after a render, so an unrelated re-render still reports the previous write's keys, making the consumer recompute when nothing changed.

(Both were first reported in #newdot-performance — which also notes a third manifestation: a cache-invalidation if/else over multiple collections' sourceValues only invalidates one collection's keys when updates batch. Both reproduce against current main: the failing-test proofs in PR #679 still pass — its description of the batching internals predates a refactor, but the per-render, last-write-wins design that causes them remains.)

Solution

Remove the sourceValue parameter from the collection callback signature and from useOnyx's result metadata. A consumer that needs the delta computes it with usePrevious (or a stored previous reference) plus a structural diff against the new collection object — O(members), O(1) per member.

On E/App, add a small pure helper getCollectionDelta (new-vs-previous collection-object diff) and a new useCollectionDelta hook, and migrate the existing sourceValue consumers onto them.

Deriving the change this way is immune to both failure modes above. A render always sees the collection in its final, fully-merged state, so a previous-vs-current diff captures every member that changed across a batch — nothing is lost. And because unchanged members keep their reference, an unrelated re-render yields an identical object and an empty diff — no stale keys, no wasted work.

Although the proposal is not performance-focused, we measured and we didn't find any performance regressions with the new approach. In fact, SidebarOrderedReportsContextProvider (a heavy user of sourceValues) showed improvements of 5-10% in total execution.

Onyx PR: Expensify/react-native-onyx#801
E/App PR: #93438

Issue OwnerCurrent Issue Owner: @mallenexpensify

Metadata

Metadata

Labels

#qualityRelates to work in the #quality roomAwaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2InternalRequires API changes or must be handled by Expensify staffTask

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions