Skip to content

Commit ba9e75e

Browse files
committed
Refactor Image for Web/Desktop with Source Headers
- Introduced `BaseImage` component that branches between native and web implementations. - **Native**: Utilizes `expo-image` directly. - **Web**: Minor adjustments made to the `onLoad` event signature for compatibility. - Eliminated `Image/index.native.js` as both native and web components now leverage a unified high-level implementation for image loading and rendering. - Added `BaseImage` specific props - Adapt to `expo-image` deprecation of `event.nativeEvent` usage. - Ensure compatibility with components using the `onLoad` prop.
1 parent 53337f5 commit ba9e75e

5 files changed

Lines changed: 85 additions & 88 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {Image as ExpoImage} from 'expo-image';
2+
import type {ImageLoadEventData} from 'expo-image';
3+
import {useCallback} from 'react';
4+
import type {BaseImageProps} from './types';
5+
6+
function BaseImage({onLoad, ...props}: BaseImageProps) {
7+
const imageLoadedSuccessfully = useCallback(
8+
(event: ImageLoadEventData) => {
9+
if (!onLoad) {
10+
return;
11+
}
12+
13+
// We override `onLoad`, so both web and native have the same signature
14+
const {width, height} = event.source;
15+
onLoad({nativeEvent: {width, height}});
16+
},
17+
[onLoad],
18+
);
19+
20+
return (
21+
<ExpoImage
22+
// Only subscribe to onLoad when a handler is provided to avoid unnecessary event registrations, optimizing performance.
23+
onLoad={onLoad ? imageLoadedSuccessfully : undefined}
24+
// eslint-disable-next-line react/jsx-props-no-spreading
25+
{...props}
26+
/>
27+
);
28+
}
29+
30+
BaseImage.displayName = 'BaseImage';
31+
32+
export default BaseImage;

src/components/Image/BaseImage.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, {useCallback} from 'react';
2+
import {Image as RNImage} from 'react-native';
3+
import type {ImageLoadEventData} from 'react-native';
4+
import type {BaseImageProps} from './types';
5+
6+
function BaseImage({onLoad, ...props}: BaseImageProps) {
7+
const imageLoadedSuccessfully = useCallback(
8+
(event: {nativeEvent: ImageLoadEventData}) => {
9+
if (!onLoad) {
10+
return;
11+
}
12+
13+
// We override `onLoad`, so both web and native have the same signature
14+
const {width, height} = event.nativeEvent.source;
15+
onLoad({nativeEvent: {width, height}});
16+
},
17+
[onLoad],
18+
);
19+
20+
return (
21+
<RNImage
22+
// Only subscribe to onLoad when a handler is provided to avoid unnecessary event registrations, optimizing performance.
23+
onLoad={onLoad ? imageLoadedSuccessfully : undefined}
24+
// eslint-disable-next-line react/jsx-props-no-spreading
25+
{...props}
26+
/>
27+
);
28+
}
29+
30+
BaseImage.displayName = 'BaseImage';
31+
32+
export default BaseImage;

src/components/Image/index.native.tsx

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/components/Image/index.tsx

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,32 @@
1-
import React, {useEffect, useMemo} from 'react';
2-
import {Image as RNImage} from 'react-native';
1+
import React, {useMemo} from 'react';
32
import {withOnyx} from 'react-native-onyx';
4-
import useNetwork from '@hooks/useNetwork';
3+
import CONST from '@src/CONST';
54
import ONYXKEYS from '@src/ONYXKEYS';
5+
import BaseImage from './BaseImage';
66
import type {ImageOnyxProps, ImageOwnProps, ImageProps} from './types';
77

8-
function Image({source: propsSource, isAuthTokenRequired = false, onLoad, session, ...forwardedProps}: ImageProps) {
9-
const {isOffline} = useNetwork();
10-
8+
function Image({source: propsSource, isAuthTokenRequired = false, session, ...forwardedProps}: ImageProps) {
119
/**
1210
* Check if the image source is a URL - if so the `encryptedAuthToken` is appended
1311
* to the source.
1412
*/
1513
const source = useMemo(() => {
1614
const authToken = session?.encryptedAuthToken ?? null;
1715
if (isAuthTokenRequired && typeof propsSource === 'object' && 'uri' in propsSource && authToken) {
18-
// There is currently a `react-native-web` bug preventing the authToken being passed
19-
// in the headers of the image request so the authToken is added as a query param.
20-
// On native the authToken IS passed in the image request headers
21-
return {uri: `${propsSource?.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`};
16+
return {
17+
...propsSource,
18+
headers: {
19+
[CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken,
20+
},
21+
};
2222
}
2323
return propsSource;
2424
// The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034.
2525
// eslint-disable-next-line react-hooks/exhaustive-deps
2626
}, [propsSource, isAuthTokenRequired]);
2727

28-
/**
29-
* The natural image dimensions are retrieved using the updated source
30-
* and as a result the `onLoad` event needs to be manually invoked to return these dimensions
31-
*/
32-
useEffect(() => {
33-
// If an onLoad callback was specified then manually call it and pass
34-
// the natural image dimensions to match the native API
35-
if (onLoad == null) {
36-
return;
37-
}
38-
39-
if (typeof source === 'object' && 'uri' in source && source.uri) {
40-
RNImage.getSize(source.uri, (width, height) => {
41-
onLoad({nativeEvent: {width, height}});
42-
});
43-
}
44-
}, [onLoad, source, isOffline]);
45-
4628
return (
47-
<RNImage
29+
<BaseImage
4830
// eslint-disable-next-line react/jsx-props-no-spreading
4931
{...forwardedProps}
5032
source={source}

src/components/Image/types.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ type ImageOnLoadEvent = {
1717
};
1818
};
1919

20-
type ImageOwnProps = {
21-
/** Styles for the Image */
22-
style?: StyleProp<ImageStyle>;
23-
20+
type BaseImageProps = {
2421
/** The static asset or URI source of the image */
2522
source: ExpoImageSource | Omit<ImageURISource, 'cache'> | ImageRequireSource | undefined;
2623

24+
/** Event for when the image is fully loaded and returns the natural dimensions of the image */
25+
onLoad?: (event: ImageOnLoadEvent) => void;
26+
};
27+
28+
type ImageOwnProps = BaseImageProps & {
29+
/** Styles for the Image */
30+
style?: StyleProp<ImageStyle>;
31+
2732
/** Should an auth token be included in the image request */
2833
isAuthTokenRequired?: boolean;
2934

@@ -39,13 +44,10 @@ type ImageOwnProps = {
3944
/** Error handler */
4045
onError?: () => void;
4146

42-
/** Event for when the image is fully loaded and returns the natural dimensions of the image */
43-
onLoad?: (event: ImageOnLoadEvent) => void;
44-
4547
/** Progress events while the image is downloading */
4648
onProgress?: () => void;
4749
};
4850

4951
type ImageProps = ImageOnyxProps & ImageOwnProps;
5052

51-
export type {ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent};
53+
export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent};

0 commit comments

Comments
 (0)