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 Owner
Current Issue Owner: @truph01
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 Owner
Current Issue Owner: @truph01