diff --git a/src/shared/components/image/image.tsx b/src/shared/components/image/image.tsx index ce5c260e1..ce5fca6f8 100644 --- a/src/shared/components/image/image.tsx +++ b/src/shared/components/image/image.tsx @@ -89,11 +89,18 @@ export function BaseImage({ const effectiveImageRequest = isInSessionCache || !enableDebounce ? rawImageRequest : debouncedImageRequest; + const imageCacheKey = src ?? rawImageRequest?.cacheKey; + const trackLoadedInInstance = enableViewport; + const [hasLoadedInInstance, setHasLoadedInInstance] = useState(false); useEffect(() => { - setHasLoadedInInstance(false); - }, [effectiveImageRequest?.cacheKey]); + if (!trackLoadedInInstance) { + return; + } + + setHasLoadedInInstance((current) => (current ? false : current)); + }, [imageCacheKey, trackLoadedInInstance]); const shouldLoadImage = Boolean( effectiveImageRequest && @@ -112,13 +119,13 @@ export function BaseImage({ }); useEffect(() => { - if (!nativeImage.isLoaded || !effectiveImageRequest?.cacheKey) { + if (!trackLoadedInInstance || !nativeImage.isLoaded || !imageCacheKey) { return; } - loadedImageCacheKeys.add(effectiveImageRequest.cacheKey); - setHasLoadedInInstance(true); - }, [effectiveImageRequest?.cacheKey, nativeImage.isLoaded]); + loadedImageCacheKeys.add(imageCacheKey); + setHasLoadedInInstance((current) => (current ? current : true)); + }, [imageCacheKey, nativeImage.isLoaded, trackLoadedInInstance]); return ( (null); const objectUrlRef = useRef(null); const onFetchErrorRef = useRef(onFetchError); + const requestRef = useRef(request); const [state, setState] = useState({ status: 'idle' }); + requestRef.current = request; + const requestSignature = useMemo(() => { if (!request) { return null; @@ -44,6 +47,8 @@ export function useNativeImage({ onFetchErrorRef.current = onFetchError; useEffect(() => { + const request = requestRef.current; + const abortCurrentRequest = () => { abortControllerRef.current?.abort(); abortControllerRef.current = null; @@ -62,28 +67,61 @@ export function useNativeImage({ if (!request || !requestSignature) { abortCurrentRequest(); revokeObjectUrl(); - setState({ status: 'idle' }); + setState((currentState) => { + if (currentState.status === 'idle' && !currentState.displaySrc) { + return currentState; + } + + return { status: 'idle' }; + }); return; } if (!enabled) { abortCurrentRequest(); - setState((currentState) => - currentState.displaySrc - ? { ...currentState, status: 'loaded' } - : { status: 'idle' }, - ); + setState((currentState) => { + if (currentState.displaySrc) { + if (currentState.status === 'loaded') { + return currentState; + } + + return { ...currentState, status: 'loaded' }; + } + + if (currentState.status === 'idle' && !currentState.displaySrc) { + return currentState; + } + + return { status: 'idle' }; + }); return; } if (loadedRequestSignatureRef.current === requestSignature && objectUrlRef.current) { - setState({ displaySrc: objectUrlRef.current, status: 'loaded' }); + const cachedObjectUrl = objectUrlRef.current; + + setState((currentState) => { + if ( + currentState.status === 'loaded' && + currentState.displaySrc === cachedObjectUrl + ) { + return currentState; + } + + return { displaySrc: cachedObjectUrl, status: 'loaded' }; + }); return; } abortCurrentRequest(); revokeObjectUrl(); - setState({ status: 'loading' }); + setState((currentState) => { + if (currentState.status === 'loading' && !currentState.displaySrc) { + return currentState; + } + + return { status: 'loading' }; + }); const abortController = new AbortController(); abortControllerRef.current = abortController; @@ -138,7 +176,7 @@ export function useNativeImage({ abortControllerRef.current = null; } }; - }, [enabled, fetchPriority, request, requestSignature]); + }, [enabled, fetchPriority, requestSignature]); useEffect(() => { return () => {