From c31b0096e3a3e704535178bf7b3cdd6ef937c66c Mon Sep 17 00:00:00 2001 From: Satyam Bansal Date: Thu, 3 Sep 2026 19:36:26 +0530 Subject: [PATCH] fix(image): emit onLoadStart before subscribing the response observer 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. --- .../ComponentViews/Image/RCTImageComponentView.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm index eeee2ed1a725..40cfa512e5c4 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm @@ -94,19 +94,23 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared & auto oldImageState = std::static_pointer_cast(_state); auto newImageState = std::static_pointer_cast(state); - [self _setStateAndResubscribeImageResponseObserver:newImageState]; - bool havePreviousData = oldImageState && oldImageState->getData().getImageSource() != ImageSource{}; if (!havePreviousData || (newImageState && newImageState->getData().getImageSource() != oldImageState->getData().getImageSource())) { // Loading actually starts a little before this, but this is the first time we know - // the image is loading and can fire an event from this component + // the image is loading and can fire an event from this component. + // + // This has to be emitted before subscribing below: the observer coordinator + // replays an already-`Completed` (or `Failed`) response synchronously, so + // subscribing first can deliver `onLoad`/`onLoadEnd` ahead of `onLoadStart`. static_cast(*_eventEmitter).onLoadStart(); // TODO (T58941612): Tracking for visibility should be done directly on this class. // For now, we consolidate instrumentation logic in the image loader, so that pre-Fabric gets the same treatment. } + + [self _setStateAndResubscribeImageResponseObserver:newImageState]; } - (void)_setStateAndResubscribeImageResponseObserver:(const ImageShadowNode::ConcreteState::Shared &)state