Skip to content

Commit 1e54c64

Browse files
committed
[add] ImageLoader.loadUsingHeaders
Move header loading logic here
1 parent 8f4d952 commit 1e54c64

File tree

3 files changed

+80
-52
lines changed

3 files changed

+80
-52
lines changed

packages/react-native-web/src/exports/Image/index.js

+33-51
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
* @flow
99
*/
1010

11-
import type { ImageProps, SourceObject } from './types';
11+
import type { ImageSource, LoadRequest } from '../../modules/ImageLoader';
12+
import type { ImageProps } from './types';
1213

1314
import * as React from 'react';
1415
import createElement from '../createElement';
@@ -146,7 +147,17 @@ function resolveAssetUri(source): ?string {
146147
return uri;
147148
}
148149

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) {
150161
return (
151162
a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers)
152163
);
@@ -287,16 +298,7 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
287298
},
288299
function error() {
289300
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 });
300302
}
301303
);
302304
}
@@ -345,55 +347,35 @@ const BaseImage: ImageComponent = React.forwardRef((props, ref) => {
345347
*/
346348
const ImageWithHeaders: ImageComponent = React.forwardRef((props, ref) => {
347349
// $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;
351351
const [blobUri, setBlobUri] = React.useState('');
352+
const request = React.useRef<LoadRequest>({
353+
cancel: () => {},
354+
source: { uri: '', headers: {} },
355+
promise: Promise.resolve('')
356+
});
352357

353-
const { onError, onLoadStart } = props;
358+
const { onError, onLoadStart, onLoadEnd } = props;
354359

355360
React.useEffect(() => {
356-
if (!hasSourceDiff(nextSource, prevSource.current)) return;
361+
if (!hasSourceDiff(nextSource, request.current.source)) return;
357362

358363
// When source changes we want to clean up any old/running requests
359-
cleanup.current();
364+
request.current.cancel();
360365

361-
prevSource.current = nextSource;
366+
if (onLoadStart) onLoadStart();
362367

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);
370369

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]);
372376

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, []);
397379

398380
const propsToPass = {
399381
...props,

packages/react-native-web/src/exports/Image/types.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type {
2020
TransformStyles
2121
} from '../../types/styles';
2222

23-
export type SourceObject = {
23+
type SourceObject = {
2424
/**
2525
* `body` is the HTTP body to send with the request. This must be a valid
2626
* UTF-8 string, and will be sent exactly as specified, with no

packages/react-native-web/src/modules/ImageLoader/index.js

+46
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,43 @@ const ImageLoader = {
144144
};
145145
image.src = uri;
146146
requests[`${id}`] = image;
147+
147148
return id;
148149
},
150+
loadWithHeaders(source: ImageSource): LoadRequest {
151+
let loadRequest: LoadRequest;
152+
let uri: string;
153+
const abortCtrl = new AbortController();
154+
const request = new Request(source.uri, {
155+
headers: source.headers,
156+
signal: abortCtrl.signal
157+
});
158+
request.headers.append('accept', 'image/*');
159+
160+
const promise = fetch(request)
161+
.then((response) => response.blob())
162+
.then((blob) => {
163+
uri = URL.createObjectURL(blob);
164+
return uri;
165+
})
166+
.catch((error) => {
167+
if (error.name === 'AbortError') {
168+
return '';
169+
}
170+
171+
throw error;
172+
});
173+
174+
return {
175+
promise,
176+
source,
177+
cancel: () => {
178+
abortCtrl.abort();
179+
if (loadRequest) loadRequest.cancel();
180+
URL.revokeObjectURL(uri);
181+
}
182+
};
183+
},
149184
prefetch(uri: string): Promise<void> {
150185
return new Promise((resolve, reject) => {
151186
ImageLoader.load(
@@ -172,4 +207,15 @@ const ImageLoader = {
172207
}
173208
};
174209

210+
export type LoadRequest = {|
211+
cancel: Function,
212+
source: ImageSource,
213+
promise: Promise<string>
214+
|};
215+
216+
export type ImageSource = {
217+
uri: string,
218+
headers: { [key: string]: string }
219+
};
220+
175221
export default ImageLoader;

0 commit comments

Comments
 (0)