88 * @flow
99 */
1010
11- import type { ImageProps } from './types' ;
11+ import type { ImageProps , SourceObject } from './types' ;
1212
1313import * as React from 'react' ;
1414import createElement from '../createElement' ;
@@ -146,6 +146,12 @@ function resolveAssetUri(source): ?string {
146146 return uri ;
147147}
148148
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+
149155interface ImageStatics {
150156 getSize : (
151157 uri : string ,
@@ -158,10 +164,12 @@ interface ImageStatics {
158164 ) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
159165}
160166
161- const Image : React . AbstractComponent <
167+ type ImageComponent = React . AbstractComponent <
162168 ImageProps ,
163169 React . ElementRef < typeof View >
164- > = React . forwardRef ( ( props , ref ) => {
170+ > ;
171+
172+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
165173 const {
166174 accessibilityLabel,
167175 blurRadius,
@@ -332,24 +340,91 @@ const Image: React.AbstractComponent<
332340 ) ;
333341} ) ;
334342
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+ } ) ;
336407
337408// $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' ;
343418
344- ImageWithStatics . getSize = function ( uri , success , failure ) {
419+ Image . getSize = function ( uri , success , failure ) {
345420 ImageLoader . getSize ( uri , success , failure ) ;
346421} ;
347422
348- ImageWithStatics . prefetch = function ( uri ) {
423+ Image . prefetch = function ( uri ) {
349424 return ImageLoader . prefetch ( uri ) ;
350425} ;
351426
352- ImageWithStatics . queryCache = function ( uris ) {
427+ Image . queryCache = function ( uris ) {
353428 return ImageLoader . queryCache ( uris ) ;
354429} ;
355430
@@ -405,4 +480,4 @@ const resizeModeStyles = StyleSheet.create({
405480 }
406481} ) ;
407482
408- export default ImageWithStatics ;
483+ export default Image ;
0 commit comments