8
8
* @flow
9
9
*/
10
10
11
+ import type { ImageSource , LoadRequest } from '../../modules/ImageLoader' ;
11
12
import type { ImageProps } from './types' ;
12
13
13
14
import * as React from 'react' ;
@@ -146,6 +147,23 @@ function resolveAssetUri(source): ?string {
146
147
return uri ;
147
148
}
148
149
150
+ function raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) {
151
+ if ( onError ) {
152
+ onError ( {
153
+ nativeEvent : {
154
+ error : `Failed to load resource ${ uri } (404)`
155
+ }
156
+ } ) ;
157
+ }
158
+ if ( onLoadEnd ) onLoadEnd ( ) ;
159
+ }
160
+
161
+ function hasSourceDiff ( a : ImageSource , b : ImageSource ) {
162
+ return (
163
+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
164
+ ) ;
165
+ }
166
+
149
167
interface ImageStatics {
150
168
getSize : (
151
169
uri : string ,
@@ -158,10 +176,12 @@ interface ImageStatics {
158
176
) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
159
177
}
160
178
161
- const Image : React . AbstractComponent <
179
+ type ImageComponent = React . AbstractComponent <
162
180
ImageProps ,
163
181
React . ElementRef < typeof View >
164
- > = React . forwardRef ( ( props , ref ) => {
182
+ > ;
183
+
184
+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
165
185
const {
166
186
accessibilityLabel,
167
187
blurRadius,
@@ -279,16 +299,7 @@ const Image: React.AbstractComponent<
279
299
} ,
280
300
function error ( ) {
281
301
updateState ( ERRORED ) ;
282
- if ( onError ) {
283
- onError ( {
284
- nativeEvent : {
285
- error : `Failed to load resource ${ uri } (404)`
286
- }
287
- } ) ;
288
- }
289
- if ( onLoadEnd ) {
290
- onLoadEnd ( ) ;
291
- }
302
+ raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) ;
292
303
}
293
304
) ;
294
305
}
@@ -332,14 +343,76 @@ const Image: React.AbstractComponent<
332
343
) ;
333
344
} ) ;
334
345
335
- Image . displayName = 'Image' ;
346
+ BaseImage . displayName = 'Image' ;
347
+
348
+ /**
349
+ * This component handles specifically loading an image source with headers
350
+ * default source is never loaded using headers
351
+ */
352
+ const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
353
+ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource`
354
+ const nextSource : ImageSource = props . source ;
355
+ const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
356
+ const request = React . useRef < LoadRequest > ( {
357
+ cancel : ( ) => { } ,
358
+ source : { uri : '' , headers : { } } ,
359
+ promise : Promise . resolve ( '' )
360
+ } ) ;
361
+
362
+ const { onError, onLoadStart, onLoadEnd } = props ;
363
+
364
+ React . useEffect ( ( ) => {
365
+ if ( ! hasSourceDiff ( nextSource , request . current . source ) ) {
366
+ return ;
367
+ }
368
+
369
+ // When source changes we want to clean up any old/running requests
370
+ request . current . cancel ( ) ;
371
+
372
+ if ( onLoadStart ) {
373
+ onLoadStart ( ) ;
374
+ }
375
+
376
+ // Store a ref for the current load request so we know what's the last loaded source,
377
+ // and so we can cancel it if a different source is passed through props
378
+ request . current = ImageLoader . loadWithHeaders ( nextSource ) ;
379
+
380
+ request . current . promise
381
+ . then ( ( uri ) => setBlobUri ( uri ) )
382
+ . catch ( ( ) =>
383
+ raiseOnErrorEvent ( request . current . source . uri , { onError, onLoadEnd } )
384
+ ) ;
385
+ } , [ nextSource , onLoadStart , onError , onLoadEnd ] ) ;
386
+
387
+ // Cancel any request on unmount
388
+ React . useEffect ( ( ) => request . current . cancel , [ ] ) ;
389
+
390
+ const propsToPass = {
391
+ ...props ,
392
+
393
+ // `onLoadStart` is called from the current component
394
+ // We skip passing it down to prevent BaseImage raising it a 2nd time
395
+ onLoadStart : undefined ,
396
+
397
+ // Until the current component resolves the request (using headers)
398
+ // we skip forwarding the source so the base component doesn't attempt
399
+ // to load the original source
400
+ source : blobUri ? { ...nextSource , uri : blobUri } : undefined
401
+ } ;
402
+
403
+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
404
+ } ) ;
336
405
337
406
// $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 ) ;
407
+ const ImageWithStatics : ImageComponent & ImageStatics = React . forwardRef (
408
+ ( props , ref ) => {
409
+ if ( props . source && props . source . headers ) {
410
+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
411
+ }
412
+
413
+ return < BaseImage ref = { ref } { ...props } /> ;
414
+ }
415
+ ) ;
343
416
344
417
ImageWithStatics . getSize = function ( uri , success , failure ) {
345
418
ImageLoader . getSize ( uri , success , failure ) ;
0 commit comments