Skip to content

Commit 8c3731a

Browse files
committed
[change] refactor Image useSource hook
1 parent 3549dda commit 8c3731a

File tree

2 files changed

+68
-58
lines changed

2 files changed

+68
-58
lines changed

packages/react-native-web/src/exports/Image/__tests__/__snapshots__/index-test.js.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,14 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
329329
>
330330
<div
331331
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
332-
style="filter: url(#tint-71);"
332+
style="filter: url(#tint-72);"
333333
/>
334334
<svg
335335
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
336336
>
337337
<defs>
338338
<filter
339-
id="tint-71"
339+
id="tint-72"
340340
>
341341
<feflood
342342
flood-color="blue"
@@ -378,7 +378,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
378378
>
379379
<div
380380
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv"
381-
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-70);"
381+
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-71);"
382382
/>
383383
<img
384384
alt=""
@@ -391,7 +391,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
391391
>
392392
<defs>
393393
<filter
394-
id="tint-70"
394+
id="tint-71"
395395
>
396396
<feflood
397397
flood-color="red"

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

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* @flow
99
*/
1010

11+
import type { ImageResult } from '../../modules/ImageLoader';
1112
import type { ImageProps, Source, ImageLoadingProps } from './types';
1213

1314
import * as React from 'react';
@@ -108,6 +109,7 @@ function resolveAssetDimensions(source) {
108109
}
109110
}
110111

112+
// Todo: move this to ImageLoader, and handle URI create by URL.createObjectURL
111113
function resolveAssetUri(source): ?string {
112114
let uri = null;
113115
if (typeof source === 'number') {
@@ -305,76 +307,84 @@ ImageWithStatics.queryCache = function (uris) {
305307
const useSource = (
306308
{ onLoad, onLoadStart, onLoadEnd, onError }: ImageLoadingProps,
307309
source: ?Source
308-
): { state: string, loadedUri: string } => {
309-
const lastLoadedSource = React.useRef();
310-
const [loadedUri, setLoadedUri] = React.useState('');
310+
): { state: string, loadedUri: ?string } => {
311+
const input = React.useRef({ uri: '' });
312+
const [requestId, setRequestId] = React.useState(-1);
311313
const [state, setState] = React.useState(() => {
312314
const uri = resolveAssetUri(source);
313-
if (uri != null) {
314-
const isLoaded = ImageLoader.has(uri);
315-
if (isLoaded) return LOADED;
316-
}
317-
return IDLE;
315+
316+
return {
317+
// Use the resolved URI for cases where it was already loaded or preloaded
318+
result: { uri },
319+
status: ImageLoader.has(uri) ? LOADED : IDLE
320+
};
318321
});
319322

320-
// This object would only change when load related fields change
321-
// We try to maintain strict object reference to prevent the effect hook running due to object change
322-
const stableSource = React.useMemo(() => {
323+
const handleLoad = React.useCallback(
324+
(result: ImageResult) => {
325+
if (onLoad) onLoad(result);
326+
if (onLoadEnd) onLoadEnd();
327+
setState({ status: LOADED, result });
328+
},
329+
[onLoad, onLoadEnd]
330+
);
331+
332+
const handleError = React.useCallback(
333+
(error) => {
334+
if (onError) {
335+
onError({
336+
nativeEvent: {
337+
error: `Failed to load resource ${input.current.uri} (404)`
338+
}
339+
});
340+
}
341+
if (onLoadEnd) onLoadEnd();
342+
343+
setState({ status: ERRORED, result: error });
344+
},
345+
[onError, onLoadEnd]
346+
);
347+
348+
const startLoading = React.useCallback(
349+
(nextInput) => {
350+
if (onLoadStart) onLoadStart();
351+
const requestId = ImageLoader.load(nextInput, handleLoad, handleError);
352+
setRequestId(requestId);
353+
354+
setState((prevState) => ({ ...prevState, status: LOADING }));
355+
},
356+
[handleError, handleLoad, onLoadStart]
357+
);
358+
359+
// Cleanup on umount or after starting a new request
360+
React.useEffect(() => {
361+
return () => {
362+
if (requestId > 0) ImageLoader.release(requestId);
363+
};
364+
}, [requestId]);
365+
366+
// (Maybe) start loading on source changes
367+
React.useEffect(() => {
323368
const uri = resolveAssetUri(source);
324-
if (uri == null) {
325-
lastLoadedSource.current = null;
326-
return null;
327-
}
369+
if (uri == null) return;
328370

329371
let headers;
330372
if (source && typeof source.headers === 'object') {
331373
headers = ((source.headers: any): { [key: string]: string });
332374
}
333375

334376
const nextInput = { uri, headers };
335-
if (
336-
JSON.stringify(nextInput) !== JSON.stringify(lastLoadedSource.current)
337-
) {
338-
lastLoadedSource.current = nextInput;
339-
}
340-
341-
return lastLoadedSource.current;
342-
}, [source]);
343377

344-
React.useEffect(() => {
345-
if (stableSource == null) return;
346-
347-
setState(LOADING);
348-
if (onLoadStart) onLoadStart();
349-
350-
const requestId = ImageLoader.load(
351-
stableSource,
352-
function load(result) {
353-
setState(LOADED);
354-
setLoadedUri(result.uri);
355-
if (onLoad) onLoad(result);
356-
if (onLoadEnd) onLoadEnd();
357-
},
358-
function error() {
359-
setState(ERRORED);
360-
if (onError) {
361-
onError({
362-
nativeEvent: {
363-
error: `Failed to load resource ${stableSource.uri} (404)`
364-
}
365-
});
366-
}
367-
if (onLoadEnd) onLoadEnd();
368-
}
369-
);
378+
// Do nothing if the input is virtually the same as the last loaded source
379+
if (JSON.stringify(nextInput) === JSON.stringify(input.current)) return;
370380

371-
const effectCleanup = () => ImageLoader.release(requestId);
372-
return effectCleanup;
373-
}, [onError, onLoad, onLoadEnd, onLoadStart, stableSource]);
381+
input.current = nextInput;
382+
startLoading(nextInput);
383+
}, [source, startLoading]);
374384

375385
return {
376-
state,
377-
loadedUri
386+
state: state.status,
387+
loadedUri: state.result.uri
378388
};
379389
};
380390

0 commit comments

Comments
 (0)