diff --git a/src/components/DeferredImageWithLoading.tsx b/src/components/DeferredImageWithLoading.tsx
new file mode 100644
index 000000000000..c7f3712f1fd9
--- /dev/null
+++ b/src/components/DeferredImageWithLoading.tsx
@@ -0,0 +1,54 @@
+import ScreenWrapperStatusContext from '@components/ScreenWrapper/ScreenWrapperStatusContext';
+
+import useRunAfterTransitions from '@hooks/useRunAfterTransitions';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+import React, {useContext} from 'react';
+import {View} from 'react-native';
+
+import type {ImageWithSizeLoadingProps} from './ImageWithLoading';
+
+import ImageWithLoading from './ImageWithLoading';
+
+/**
+ * Wrapper around ImageWithLoading that keeps the image out of the render passes happening during a screen's entry
+ * transition: fetching, decoding and laying out an image competes with the animation, so the screen slides in with an
+ * empty placeholder of the same size and the image mounts once the transition is over.
+ *
+ * The gate is the enclosing ScreenWrapper's `didScreenTransitionEnd`, not `useRunAfterTransitions(true)` alone: an
+ * incoming screen's subtree mounts (and runs its effects) *before* React Navigation emits `transitionStart`, so at that
+ * point TransitionTracker has no active transition and would let the image through immediately.
+ *
+ * On a screen that has already settled - a receipt arriving in an open chat - `didScreenTransitionEnd` is already true,
+ * so the image mounts on the very next render with no perceptible delay.
+ */
+function DeferredImageWithLoading({containerStyles, onLayout, ...rest}: ImageWithSizeLoadingProps) {
+ const styles = useThemeStyles();
+
+ // Read the context directly rather than through `useScreenWrapperTransitionStatus`, which throws outside a
+ // ScreenWrapper. There is no screen entry transition to wait for in that case, so don't hold the image back.
+ const screenWrapperStatus = useContext(ScreenWrapperStatusContext);
+ const didScreenTransitionEnd = screenWrapperStatus?.didScreenTransitionEnd ?? true;
+
+ // Also wait out any transition still tracked once the screen has settled, e.g. an overlapping modal or keyboard one.
+ const shouldRenderImage = useRunAfterTransitions(didScreenTransitionEnd);
+
+ if (!shouldRenderImage) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export default DeferredImageWithLoading;
diff --git a/src/components/ImageWithLoading.tsx b/src/components/ImageWithLoading.tsx
index 508960051142..fcaadda8e235 100644
--- a/src/components/ImageWithLoading.tsx
+++ b/src/components/ImageWithLoading.tsx
@@ -149,3 +149,4 @@ function ImageWithLoading({
ImageWithLoading.displayName = 'ImageWithLoading';
export default React.memo(ImageWithLoading);
+export type {ImageWithSizeLoadingProps};
diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx
index 7710611bda61..777455c1cb32 100644
--- a/src/components/ImageWithSizeCalculation.tsx
+++ b/src/components/ImageWithSizeCalculation.tsx
@@ -12,8 +12,8 @@ import React, {useMemo} from 'react';
import type {FullScreenLoadingIndicatorIconSize} from './FullscreenLoadingIndicator';
import type {ImageObjectPosition} from './Image/types';
+import DeferredImageWithLoading from './DeferredImageWithLoading';
import RESIZE_MODES from './Image/resizeModes';
-import ImageWithLoading from './ImageWithLoading';
type OnMeasure = (args: {width: number; height: number}) => void;
@@ -95,7 +95,7 @@ function ImageWithSizeCalculation({
};
return (
- {
if (e.nativeEvent.layout.width !== receiptImageWidth && e.timeStamp - lastUpdateWidthTimestampRef.current > MIN_UPDATE_WIDTH_DIFF) {
setReceiptImageWidth(e.nativeEvent.layout.width);
diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx
index 8d9d6a7a4a0a..73f46ded020d 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImage.tsx
@@ -191,7 +191,7 @@ function ReportActionItemImage({
propsObj = {
shouldUseThumbnailImage: shouldUseThumbnailImage ?? true,
- source: thumbnailSource,
+ source: originalImageSource,
fallbackIcon: icons.Receipt,
fallbackIconSize: isSingleImage ? variables.iconSizeSuperLarge : variables.iconSizeExtraLarge,
isAuthTokenRequired: true,