Skip to content

Commit a95d883

Browse files
committed
[change] refactor Image useSource hook
1 parent a31d911 commit a95d883

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') {
@@ -310,76 +312,84 @@ ImageWithStatics.queryCache = function (uris) {
310312
const useSource = (
311313
{ onLoad, onLoadStart, onLoadEnd, onError }: ImageLoadingProps,
312314
source: ?Source
313-
): { state: string, loadedUri: string } => {
314-
const lastLoadedSource = React.useRef();
315-
const [loadedUri, setLoadedUri] = React.useState('');
315+
): { state: string, loadedUri: ?string } => {
316+
const input = React.useRef({ uri: '' });
317+
const [requestId, setRequestId] = React.useState(-1);
316318
const [state, setState] = React.useState(() => {
317319
const uri = resolveAssetUri(source);
318-
if (uri != null) {
319-
const isLoaded = ImageLoader.has(uri);
320-
if (isLoaded) return LOADED;
321-
}
322-
return IDLE;
320+
321+
return {
322+
// Use the resolved URI for cases where it was already loaded or preloaded
323+
result: { uri },
324+
status: ImageLoader.has(uri) ? LOADED : IDLE
325+
};
323326
});
324327

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

334376
let headers;
335377
if (source && typeof source.headers === 'object') {
336378
headers = ((source.headers: any): { [key: string]: string });
337379
}
338380

339381
const nextInput = { uri, headers };
340-
if (
341-
JSON.stringify(nextInput) !== JSON.stringify(lastLoadedSource.current)
342-
) {
343-
lastLoadedSource.current = nextInput;
344-
}
345-
346-
return lastLoadedSource.current;
347-
}, [source]);
348382

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

376-
const effectCleanup = () => ImageLoader.release(requestId);
377-
return effectCleanup;
378-
}, [onError, onLoad, onLoadEnd, onLoadStart, stableSource]);
386+
input.current = nextInput;
387+
startLoading(nextInput);
388+
}, [source, startLoading]);
379389

380390
return {
381-
state,
382-
loadedUri
391+
state: state.status,
392+
loadedUri: state.result.uri
383393
};
384394
};
385395

0 commit comments

Comments
 (0)