|
8 | 8 | * @flow
|
9 | 9 | */
|
10 | 10 |
|
11 |
| -import type { ImageProps, SourceObject } from './types'; |
| 11 | +import type { ImageSource, LoadRequest } from '../../modules/ImageLoader'; |
| 12 | +import type { ImageProps } from './types'; |
12 | 13 |
|
13 | 14 | import * as React from 'react';
|
14 | 15 | import createElement from '../createElement';
|
@@ -146,7 +147,17 @@ function resolveAssetUri(source): ?string {
|
146 | 147 | return uri;
|
147 | 148 | }
|
148 | 149 |
|
149 |
| -function hasSourceDiff(a: SourceObject, b: SourceObject) { |
| 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 | +function hasSourceDiff(a: ImageSource, b: ImageSource) { |
150 | 161 | return (
|
151 | 162 | a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers)
|
152 | 163 | );
|
@@ -287,16 +298,7 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
|
287 | 298 | },
|
288 | 299 | function error() {
|
289 | 300 | updateState(ERRORED);
|
290 |
| - if (onError) { |
291 |
| - onError({ |
292 |
| - nativeEvent: { |
293 |
| - error: `Failed to load resource ${uri} (404)` |
294 |
| - } |
295 |
| - }); |
296 |
| - } |
297 |
| - if (onLoadEnd) { |
298 |
| - onLoadEnd(); |
299 |
| - } |
| 301 | + raiseOnErrorEvent(uri, { onError, onLoadEnd }); |
300 | 302 | }
|
301 | 303 | );
|
302 | 304 | }
|
@@ -345,55 +347,35 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
|
345 | 347 | */
|
346 | 348 | const ImageWithHeaders: ImageComponent = React.forwardRef((props, ref) => {
|
347 | 349 | // $FlowIgnore
|
348 |
| - const nextSource: SourceObject = props.source; |
349 |
| - const prevSource = React.useRef<SourceObject>({}); |
350 |
| - const cleanup = React.useRef<Function>(() => {}); |
| 350 | + const nextSource: ImageSource = props.source; |
351 | 351 | const [blobUri, setBlobUri] = React.useState('');
|
| 352 | + const request = React.useRef<LoadRequest>({ |
| 353 | + cancel: () => {}, |
| 354 | + source: { uri: '', headers: {} }, |
| 355 | + promise: Promise.resolve('') |
| 356 | + }); |
352 | 357 |
|
353 |
| - const { onError, onLoadStart } = props; |
| 358 | + const { onError, onLoadStart, onLoadEnd } = props; |
354 | 359 |
|
355 | 360 | React.useEffect(() => {
|
356 |
| - if (!hasSourceDiff(nextSource, prevSource.current)) return; |
| 361 | + if (!hasSourceDiff(nextSource, request.current.source)) return; |
357 | 362 |
|
358 | 363 | // When source changes we want to clean up any old/running requests
|
359 |
| - cleanup.current(); |
| 364 | + request.current.cancel(); |
360 | 365 |
|
361 |
| - prevSource.current = nextSource; |
| 366 | + if (onLoadStart) onLoadStart(); |
362 | 367 |
|
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/*'); |
| 368 | + request.current = ImageLoader.loadWithHeaders(nextSource); |
370 | 369 |
|
371 |
| - if (onLoadStart) onLoadStart(); |
| 370 | + request.current.promise |
| 371 | + .then((uri) => setBlobUri(uri)) |
| 372 | + .catch(() => |
| 373 | + raiseOnErrorEvent(request.current.source.uri, { onError, onLoadEnd }) |
| 374 | + ); |
| 375 | + }, [nextSource, onLoadStart, onError, onLoadEnd]); |
372 | 376 |
|
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, []); |
| 377 | + // Cancel any request on unmount |
| 378 | + React.useEffect(() => request.current.cancel, []); |
397 | 379 |
|
398 | 380 | const propsToPass = {
|
399 | 381 | ...props,
|
|
0 commit comments