8
8
* @flow
9
9
*/
10
10
11
- import type { ImageProps } from './types' ;
11
+ import type { ImageProps , SourceObject } from './types' ;
12
12
13
13
import * as React from 'react' ;
14
14
import createElement from '../createElement' ;
@@ -146,6 +146,12 @@ function resolveAssetUri(source): ?string {
146
146
return uri ;
147
147
}
148
148
149
+ function hasSourceDiff ( a : SourceObject , b : SourceObject ) {
150
+ return (
151
+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
152
+ ) ;
153
+ }
154
+
149
155
interface ImageStatics {
150
156
getSize : (
151
157
uri : string ,
@@ -158,10 +164,12 @@ interface ImageStatics {
158
164
) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
159
165
}
160
166
161
- const Image : React . AbstractComponent <
167
+ type ImageComponent = React . AbstractComponent <
162
168
ImageProps ,
163
169
React . ElementRef < typeof View >
164
- > = React . forwardRef ( ( props , ref ) => {
170
+ > ;
171
+
172
+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
165
173
const {
166
174
accessibilityLabel,
167
175
blurRadius,
@@ -332,24 +340,91 @@ const Image: React.AbstractComponent<
332
340
) ;
333
341
} ) ;
334
342
335
- Image . displayName = 'Image' ;
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 load start because we already triggered it here
401
+ onLoadStart : undefined ,
402
+ source : { ...nextSource , uri : blobUri }
403
+ } ;
404
+
405
+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
406
+ } ) ;
336
407
337
408
// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
338
- const ImageWithStatics = ( Image : React . AbstractComponent <
339
- ImageProps ,
340
- React . ElementRef < typeof View >
341
- > &
342
- ImageStatics ) ;
409
+ const Image : ImageComponent & ImageStatics = React . forwardRef ( ( props , ref ) => {
410
+ if ( props . source && props . source . headers ) {
411
+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
412
+ }
413
+
414
+ return < BaseImage ref = { ref } { ...props } /> ;
415
+ } ) ;
416
+
417
+ Image . displayName = 'Image' ;
343
418
344
- ImageWithStatics . getSize = function ( uri , success , failure ) {
419
+ Image . getSize = function ( uri , success , failure ) {
345
420
ImageLoader . getSize ( uri , success , failure ) ;
346
421
} ;
347
422
348
- ImageWithStatics . prefetch = function ( uri ) {
423
+ Image . prefetch = function ( uri ) {
349
424
return ImageLoader . prefetch ( uri ) ;
350
425
} ;
351
426
352
- ImageWithStatics . queryCache = function ( uris ) {
427
+ Image . queryCache = function ( uris ) {
353
428
return ImageLoader . queryCache ( uris ) ;
354
429
} ;
355
430
@@ -405,4 +480,4 @@ const resizeModeStyles = StyleSheet.create({
405
480
}
406
481
} ) ;
407
482
408
- export default ImageWithStatics ;
483
+ export default Image ;
0 commit comments