Skip to content

Commit 48240e3

Browse files
committed
perf: optimize images loading state
1 parent 087df9b commit 48240e3

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

package/src/components/Attachment/Gallery.tsx

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import React, { useMemo } from 'react';
2-
import { Pressable, StyleSheet, Text, View } from 'react-native';
1+
import React, { useEffect, useMemo, useRef } from 'react';
2+
import {
3+
ImageErrorEventData,
4+
NativeSyntheticEvent,
5+
Pressable,
6+
StyleSheet,
7+
Text,
8+
View,
9+
} from 'react-native';
310

411
import type { Attachment, LocalMessage } from 'stream-chat';
512

@@ -35,6 +42,7 @@ import {
3542
import { useTheme } from '../../contexts/themeContext/ThemeContext';
3643

3744
import { useLoadingImage } from '../../hooks/useLoadingImage';
45+
import { useStableCallback } from '../../hooks/useStableCallback';
3846
import { isVideoPlayerAvailable } from '../../native';
3947
import { primitives } from '../../theme';
4048
import { FileTypes } from '../../types/types';
@@ -66,6 +74,8 @@ export type GalleryPropsWithContext = Pick<ImageGalleryContextValue, 'imageGalle
6674
messageHasOnlyOneImage: boolean;
6775
};
6876

77+
const IMAGE_LOADING_INDICATOR_DELAY_MS = 120;
78+
6979
const GalleryWithContext = (props: GalleryPropsWithContext) => {
7080
const {
7181
additionalPressableProps,
@@ -385,6 +395,7 @@ const GalleryImageThumbnail = ({
385395
setLoadingImage,
386396
setLoadingImageError,
387397
} = useLoadingImage();
398+
const loadingIndicatorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
388399

389400
const {
390401
theme: {
@@ -394,20 +405,49 @@ const GalleryImageThumbnail = ({
394405

395406
const styles = useStyles();
396407

408+
const clearLoadingIndicatorTimer = useStableCallback(() => {
409+
if (loadingIndicatorTimerRef.current) {
410+
clearTimeout(loadingIndicatorTimerRef.current);
411+
loadingIndicatorTimerRef.current = null;
412+
}
413+
});
414+
415+
const onLoadStart = useStableCallback(() => {
416+
clearLoadingIndicatorTimer();
417+
setLoadingImageError(false);
418+
loadingIndicatorTimerRef.current = setTimeout(() => {
419+
setLoadingImage(true);
420+
loadingIndicatorTimerRef.current = null;
421+
}, IMAGE_LOADING_INDICATOR_DELAY_MS);
422+
});
423+
424+
const onLoadEnd = useStableCallback(() => {
425+
clearLoadingIndicatorTimer();
426+
setLoadingImage(false);
427+
setLoadingImageError(false);
428+
});
429+
430+
const onError = useStableCallback(
431+
({ nativeEvent: { error } }: NativeSyntheticEvent<ImageErrorEventData>) => {
432+
clearLoadingIndicatorTimer();
433+
console.warn(error);
434+
setLoadingImage(false);
435+
setLoadingImageError(true);
436+
},
437+
);
438+
439+
useEffect(() => clearLoadingIndicatorTimer, [clearLoadingIndicatorTimer]);
440+
397441
return (
398442
<View style={[styles.image, borderRadius]}>
399443
{isLoadingImageError ? (
400444
<ImageLoadingFailedIndicator onReloadImage={onReloadImage} />
401445
) : (
402446
<>
403447
<GalleryImage
404-
onError={({ nativeEvent: { error } }) => {
405-
console.warn(error);
406-
setLoadingImage(false);
407-
setLoadingImageError(true);
408-
}}
409-
onLoadEnd={() => setTimeout(() => setLoadingImage(false), 0)}
410-
onLoadStart={() => setLoadingImage(true)}
448+
onError={onError}
449+
onLoadEnd={onLoadEnd}
450+
onLoadStart={onLoadStart}
411451
resizeMode={thumbnail.resizeMode}
412452
style={gallery.image}
413453
uri={thumbnail.url}

package/src/hooks/useLoadingImage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,23 @@ type Action =
1515
function reducer(prevState: ImageState, action: Action) {
1616
switch (action.type) {
1717
case 'reloadImage':
18+
if (prevState.isLoadingImage && !prevState.isLoadingImageError) {
19+
return prevState;
20+
}
1821
return {
1922
...prevState,
2023
isLoadingImage: true,
2124
isLoadingImageError: false,
2225
};
2326
case 'setLoadingImage':
27+
if (prevState.isLoadingImage === action.isLoadingImage) {
28+
return prevState;
29+
}
2430
return { ...prevState, isLoadingImage: action.isLoadingImage };
2531
case 'setLoadingImageError':
32+
if (prevState.isLoadingImageError === action.isLoadingImageError) {
33+
return prevState;
34+
}
2635
return { ...prevState, isLoadingImageError: action.isLoadingImageError };
2736
default:
2837
return prevState;

0 commit comments

Comments
 (0)