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' ;
@@ -151,6 +151,12 @@ function resolveAssetUri(source): ?string {
151
151
return uri ;
152
152
}
153
153
154
+ function hasSourceDiff ( a : SourceObject , b : SourceObject ) {
155
+ return (
156
+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
157
+ ) ;
158
+ }
159
+
154
160
interface ImageStatics {
155
161
getSize : (
156
162
uri : string ,
@@ -163,10 +169,12 @@ interface ImageStatics {
163
169
) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
164
170
}
165
171
166
- const Image : React . AbstractComponent <
172
+ type ImageComponent = React . AbstractComponent <
167
173
ImageProps ,
168
174
React . ElementRef < typeof View >
169
- > = React . forwardRef ( ( props , ref ) => {
175
+ > ;
176
+
177
+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
170
178
const {
171
179
accessibilityLabel,
172
180
blurRadius,
@@ -337,24 +345,91 @@ const Image: React.AbstractComponent<
337
345
) ;
338
346
} ) ;
339
347
340
- Image . displayName = 'Image' ;
348
+ /**
349
+ * This component handles specifically loading an image source with header
350
+ */
351
+ const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
352
+ // $FlowIgnore
353
+ const nextSource : SourceObject = props . source ;
354
+ const prevSource = React . useRef < SourceObject > ( { } ) ;
355
+ const cleanup = React . useRef < Function > ( ( ) => { } ) ;
356
+ const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
357
+
358
+ const { onError, onLoadStart } = props ;
359
+
360
+ React . useEffect ( ( ) => {
361
+ if ( ! hasSourceDiff ( nextSource , prevSource . current ) ) return ;
362
+
363
+ // When source changes we want to clean up any old/running requests
364
+ cleanup . current ( ) ;
365
+
366
+ prevSource . current = nextSource ;
367
+
368
+ let uri ;
369
+ const abortCtrl = new AbortController ( ) ;
370
+ const request = new Request ( nextSource . uri , {
371
+ headers : nextSource . headers ,
372
+ signal : abortCtrl . signal
373
+ } ) ;
374
+ request . headers . append ( 'accept' , 'image/*' ) ;
375
+
376
+ if ( onLoadStart ) onLoadStart ( ) ;
377
+
378
+ fetch ( request )
379
+ . then ( ( response ) => response . blob ( ) )
380
+ . then ( ( blob ) => {
381
+ uri = URL . createObjectURL ( blob ) ;
382
+ setBlobUri ( uri ) ;
383
+ } )
384
+ . catch ( ( error ) => {
385
+ if ( error . name !== 'AbortError' && onError ) {
386
+ onError ( { nativeEvent : error . message } ) ;
387
+ }
388
+ } ) ;
389
+
390
+ // Capture a cleanup function for the current request
391
+ // The reason for using a Ref is to avoid making this function a dependency
392
+ // Because the change of a dependency would otherwise would re-trigger a hook
393
+ cleanup . current = ( ) => {
394
+ abortCtrl . abort ( ) ;
395
+ setBlobUri ( '' ) ;
396
+ URL . revokeObjectURL ( uri ) ;
397
+ } ;
398
+ } , [ nextSource , onLoadStart , onError ] ) ;
399
+
400
+ // Run the cleanup function on unmount
401
+ React . useEffect ( ( ) => cleanup . current , [ ] ) ;
402
+
403
+ const propsToPass = {
404
+ ...props ,
405
+ // Omit load start because we already triggered it here
406
+ onLoadStart : undefined ,
407
+ source : { ...nextSource , uri : blobUri }
408
+ } ;
409
+
410
+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
411
+ } ) ;
341
412
342
413
// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
343
- const ImageWithStatics = ( Image : React . AbstractComponent <
344
- ImageProps ,
345
- React . ElementRef < typeof View >
346
- > &
347
- ImageStatics ) ;
414
+ const Image : ImageComponent & ImageStatics = React . forwardRef ( ( props , ref ) => {
415
+ if ( props . source && props . source . headers ) {
416
+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
417
+ }
418
+
419
+ return < BaseImage ref = { ref } { ...props } /> ;
420
+ } ) ;
421
+
422
+ Image . displayName = 'Image' ;
348
423
349
- ImageWithStatics . getSize = function ( uri , success , failure ) {
424
+ Image . getSize = function ( uri , success , failure ) {
350
425
ImageLoader . getSize ( uri , success , failure ) ;
351
426
} ;
352
427
353
- ImageWithStatics . prefetch = function ( uri ) {
428
+ Image . prefetch = function ( uri ) {
354
429
return ImageLoader . prefetch ( uri ) ;
355
430
} ;
356
431
357
- ImageWithStatics . queryCache = function ( uris ) {
432
+ Image . queryCache = function ( uris ) {
358
433
return ImageLoader . queryCache ( uris ) ;
359
434
} ;
360
435
@@ -410,4 +485,4 @@ const resizeModeStyles = StyleSheet.create({
410
485
}
411
486
} ) ;
412
487
413
- export default ImageWithStatics ;
488
+ export default Image ;
0 commit comments