88 * @flow
99 */
1010
11- import type { ImageProps , SourceObject } from './types' ;
11+ import type { LoadRequest } from '../../modules/ImageLoader' ;
12+ import type { ImageProps } from './types' ;
1213
1314import * as React from 'react' ;
1415import createElement from '../createElement' ;
@@ -146,7 +147,7 @@ function resolveAssetUri(source): ?string {
146147 return uri ;
147148}
148149
149- function hasSourceDiff ( a : SourceObject , b : SourceObject ) {
150+ function hasSourceDiff ( a , b ) {
150151 return (
151152 a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
152153 ) ;
@@ -164,12 +165,10 @@ interface ImageStatics {
164165 ) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
165166}
166167
167- type ImageComponent = React . AbstractComponent <
168+ const Image : React . AbstractComponent <
168169 ImageProps ,
169170 React . ElementRef < typeof View >
170- > ;
171-
172- const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
171+ > = React . forwardRef ( ( props , ref ) => {
173172 const {
174173 accessibilityLabel,
175174 blurRadius,
@@ -194,6 +193,7 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
194193 }
195194 }
196195
196+ const lastLoadedSource = React . useRef ( { } ) ;
197197 const [ state , updateState ] = React . useState ( ( ) => {
198198 const uri = resolveAssetUri ( source ) ;
199199 if ( uri != null ) {
@@ -209,7 +209,10 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
209209 const hasTextAncestor = React . useContext ( TextAncestorContext ) ;
210210 const hiddenImageRef = React . useRef ( null ) ;
211211 const filterRef = React . useRef ( _filterId ++ ) ;
212- const requestRef = React . useRef ( null ) ;
212+ const requestRef = React . useRef < LoadRequest > ( {
213+ cancel : ( ) => { } ,
214+ requestId : - 1
215+ } ) ;
213216 const shouldDisplaySource =
214217 state === LOADED || ( state === LOADING && defaultSource == null ) ;
215218 const [ flatStyle , _resizeMode , filter , tintColor ] = getFlatStyle (
@@ -266,16 +269,27 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
266269 // Image loading
267270 const uri = resolveAssetUri ( source ) ;
268271 React . useEffect ( ( ) => {
269- abortPendingRequest ( ) ;
270-
271272 if ( uri != null ) {
273+ const nextSource = {
274+ // $FlowFixMe
275+ headers : source . headers ,
276+ uri
277+ } ;
278+ if ( ! hasSourceDiff ( nextSource , lastLoadedSource . current ) ) return ;
279+
280+ requestRef . current . cancel ( ) ;
272281 updateState ( LOADING ) ;
273282 if ( onLoadStart ) {
274283 onLoadStart ( ) ;
275284 }
276285
277- requestRef . current = ImageLoader . load (
278- uri ,
286+ const makeRequest = nextSource . headers
287+ ? ImageLoader . loadWithHeaders
288+ : ImageLoader . load ;
289+
290+ requestRef . current = makeRequest (
291+ // $FlowFixMe
292+ nextSource ,
279293 function load ( e ) {
280294 updateState ( LOADED ) ;
281295 if ( onLoad ) {
@@ -300,17 +314,11 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
300314 }
301315 ) ;
302316 }
303-
304- function abortPendingRequest ( ) {
305- if ( requestRef . current != null ) {
306- ImageLoader . abort ( requestRef . current ) ;
307- requestRef . current = null ;
308- }
309- }
310-
311- return abortPendingRequest ;
312317 } , [ uri , requestRef , updateState , onError , onLoad , onLoadEnd , onLoadStart ] ) ;
313318
319+ // Run the cleanup function on unmount
320+ React . useEffect ( ( ) => requestRef . current . cancel , [ ] ) ;
321+
314322 return (
315323 < View
316324 { ...rest }
@@ -340,94 +348,24 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
340348 ) ;
341349} ) ;
342350
343- /**
344- * This component handles specifically loading an image source with header
345- */
346- const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
347- // $FlowIgnore
348- const nextSource : SourceObject = props . source ;
349- const prevSource = React . useRef < SourceObject > ( { } ) ;
350- const cleanup = React . useRef < Function > ( ( ) => { } ) ;
351- const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
352-
353- const { onError, onLoadStart } = props ;
354-
355- React . useEffect ( ( ) => {
356- if ( ! hasSourceDiff ( nextSource , prevSource . current ) ) return ;
357-
358- // When source changes we want to clean up any old/running requests
359- cleanup . current ( ) ;
360-
361- prevSource . current = nextSource ;
362-
363- let uri ;
364- const abortCtrl = new AbortController ( ) ;
365- const request = new Request ( nextSource . uri , {
366- headers : nextSource . headers ,
367- signal : abortCtrl . signal
368- } ) ;
369- request . headers . append ( 'accept' , 'image/*' ) ;
370-
371- if ( onLoadStart ) onLoadStart ( ) ;
372-
373- fetch ( request )
374- . then ( ( response ) => response . blob ( ) )
375- . then ( ( blob ) => {
376- uri = URL . createObjectURL ( blob ) ;
377- setBlobUri ( uri ) ;
378- } )
379- . catch ( ( error ) => {
380- if ( error . name !== 'AbortError' && onError ) {
381- onError ( { nativeEvent : error . message } ) ;
382- }
383- } ) ;
384-
385- // Capture a cleanup function for the current request
386- // The reason for using a Ref is to avoid making this function a dependency
387- // Because the change of a dependency would otherwise would re-trigger a hook
388- cleanup . current = ( ) => {
389- abortCtrl . abort ( ) ;
390- setBlobUri ( '' ) ;
391- URL . revokeObjectURL ( uri ) ;
392- } ;
393- } , [ nextSource , onLoadStart , onError ] ) ;
394-
395- // Run the cleanup function on unmount
396- React . useEffect ( ( ) => cleanup . current , [ ] ) ;
397-
398- const propsToPass = {
399- ...props ,
400- // Omit `onLoadStart` because we trigger it in the current scope
401- onLoadStart : undefined ,
402- // Until the current component resolves the request (using headers)
403- // we skip forwarding the source so the base component doesn't attempt
404- // to load the original source
405- source : blobUri ? { ...nextSource , uri : blobUri } : undefined
406- } ;
407-
408- return < BaseImage ref = { ref } { ...propsToPass } /> ;
409- } ) ;
351+ Image . displayName = 'Image' ;
410352
411353// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
412- const Image : ImageComponent & ImageStatics = React . forwardRef ( ( props , ref ) => {
413- if ( props . source && props . source . headers ) {
414- return < ImageWithHeaders ref = { ref } { ...props } /> ;
415- }
416-
417- return < BaseImage ref = { ref } { ...props } /> ;
418- } ) ;
419-
420- Image . displayName = 'Image' ;
354+ const ImageWithStatics = ( Image : React . AbstractComponent <
355+ ImageProps ,
356+ React . ElementRef < typeof View >
357+ > &
358+ ImageStatics ) ;
421359
422- Image . getSize = function ( uri , success , failure ) {
360+ ImageWithStatics . getSize = function ( uri , success , failure ) {
423361 ImageLoader . getSize ( uri , success , failure ) ;
424362} ;
425363
426- Image . prefetch = function ( uri ) {
364+ ImageWithStatics . prefetch = function ( uri ) {
427365 return ImageLoader . prefetch ( uri ) ;
428366} ;
429367
430- Image . queryCache = function ( uris ) {
368+ ImageWithStatics . queryCache = function ( uris ) {
431369 return ImageLoader . queryCache ( uris ) ;
432370} ;
433371
@@ -483,4 +421,4 @@ const resizeModeStyles = StyleSheet.create({
483421 }
484422} ) ;
485423
486- export default Image ;
424+ export default ImageWithStatics ;
0 commit comments