Skip to content

fix(image): emit onLoadStart before subscribing the response observer on Fabric - #58314

Open
SatyamBansal wants to merge 1 commit into
react:mainfrom
SatyamBansal:fix/fabric-image-onloadstart-order
Open

fix(image): emit onLoadStart before subscribing the response observer on Fabric#58314
SatyamBansal wants to merge 1 commit into
react:mainfrom
SatyamBansal:fix/fabric-image-onloadstart-order

Conversation

@SatyamBansal

Copy link
Copy Markdown

Summary

Fixes #54120.

On the New Architecture on iOS, changing the source of an already-mounted <Image> delivers the load events out of order:

onLoad -> onLoadEnd -> onLoadStart      // onLoadStart arrives LAST

RCTImageComponentView.updateState:oldState: subscribes the image response observer before it emits onLoadStart(). ImageResponseObserverCoordinator::addObserver does not queue a finished request — it replays it synchronously (case Completed:observer->didReceiveImage(...), and case Failed:didReceiveFailure(...)). RCTImageResponseObserverProxy forwards through RCTExecuteOnMainQueue, which is if (RCTIsMainQueue()) { block(); } — inline, and mounting is on the main thread. So when a request has already completed by the time the mount transaction applies, didReceiveImage: emits onLoad + onLoadEnd from inside the subscribe call, and onLoadStart follows afterwards.

This moves the onLoadStart() emission above the subscribe call, restoring the ordering the old architecture guarantees — RCTImageView.reloadImage emits _onLoadStart before it calls loadImageWithURLRequest:, so it cannot invert. Android is likewise unaffected: ReactImageView binds onLoadStart to Fresco's onSubmit and onLoad/onLoadEnd to onFinalImageSet, and submission always precedes delivery.

The move is safe with respect to the guard condition: both oldImageState and newImageState are captured from _state / state before the subscribe call, so hoisting the emission above it does not change what the condition sees.

Why it matters

Any component that shows a spinner on onLoadStart and hides it on onLoadEnd is left with a permanently visible spinner over a fully decoded image — the event that turns it on arrives after the event that would turn it off. #54120 has reports of this from three separate users; the only workaround in the thread is a guessed setTimeout.

Changelog:

[IOS] [FIXED] - Emit Image's onLoadStart before onLoad/onLoadEnd when the image request has already completed

Test Plan

Reproducer (community template, only App.tsx differs): https://github.com/SatyamBansal/rn-repro-image-onloadstart-order

It mounts four <Image>s and records the order of onLoadStart / onLoad / onLoadEnd using a counter incremented inside each callback (so the log is the order events reached JS, not a render artifact), with performance.now() timestamps. Pressing Swap E source changes one mounted <Image>'s source between two data: URIs.

Verified on an iPhone 17 Pro simulator, iOS 26.3, New Architecture, debug, react-native 0.87.1 built from source (RCT_USE_PREBUILT_RNCORE=0, so this file is actually compiled — with the default prebuilt core a local edit here has no effect).

Before — swap E's source:

#1  912798714.91  onLoad      E
#2  912798715.06  onLoadEnd   E
#3  912798715.08  onLoadStart E

Reproducible on every press. All three events land within 0.2 ms, inside one mount transaction — not an asynchronous race.

After this patch, same build, same gesture:

#1  913716961.86  onLoadStart E
#2  913716961.92  onLoad      E
#3  913716961.95  onLoadEnd   E
#1  913727913.01  onLoadStart E
#2  913727913.15  onLoad      E
#3  913727913.21  onLoadEnd   E

First-mount ordering is unchanged (it was already correct — on a fresh mount the request is still Loading when the view subscribes, because RCTImageManager.requestImage dispatches the fetch to a background queue after returning).

Not covered by this test plan, and not run: Android and the iOS old architecture. Neither goes through this file, and both are argued above from source rather than measured.

On Fabric, updateState: subscribed the image response observer before it
emitted onLoadStart. ImageResponseObserverCoordinator::addObserver replays
an already-Completed (or Failed) response synchronously, and
RCTExecuteOnMainQueue runs the block inline when already on the main queue
(which mounting is) - so a request that finished before the mount
transaction applied delivered onLoad and onLoadEnd ahead of onLoadStart.

Moving the emission above the subscribe call restores the ordering the old
architecture guarantees (RCTImageView.reloadImage emits _onLoadStart before
calling the loader). Both state locals are captured before the subscribe
call, so the condition is unaffected by the move.
@meta-cla

meta-cla Bot commented Sep 3, 2026

Copy link
Copy Markdown

Hi @SatyamBansal!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 3, 2026
@meta-cla

meta-cla Bot commented Sep 3, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

1 participant