Based on the answers above, I made, with TypeScript, a functional component that downloads the image only once (because the second time it will be cached: https://reactnative.dev/docs/image#getsize), if only one value is passed; and that calculates both height and width, depending on the property that was passed
import { useFocusEffect } from '@react-navigation/native';
import React from 'react';
import { ImageProps, ImageURISource } from 'react-native';
import { useIsMounted } from '../../hooks/is-mounted';
import { DrImageStyl } from './styled';
import { getImageSizes } from '../../utils/util';
interface DrSource extends ImageURISource {
uri: string;
}
interface DrImageProps extends ImageProps {
source: DrSource;
width?: number;
height?: number;
}
const DrImage: React.FC<DrImageProps> = ({
width: widthProp,
height: heightProp,
source,
...rest
}: DrImageProps) => {
const isMountedRef = useIsMounted();
const [sizes, setSizes] = React.useState({
width: widthProp,
height: heightProp,
});
useFocusEffect(
React.useCallback(() => {
const getImageSizesState = async () => {
try {
const { width, height } = await getImageSizes({
uri: source.uri,
width: widthProp,
height: heightProp,
});
if (isMountedRef.current) {
setSizes({ width, height });
}
} catch (error) {
console.log('Erro em dr-image getImageSizesState:', error);
}
};
getImageSizesState();
}, [widthProp, heightProp, source.uri])
);
return (
<>
{!!sizes.height && !!sizes.width && (
<DrImageStyl sizes={sizes} source={source} {...rest} />
)}
</>
);
};
export default DrImage;
I used a hook to determine if, after the asynchronous function, the component is still mounted (useIsMounted):
import React from 'react';
export const useIsMounted = (): React.MutableRefObject<boolean> => {
const isMountedRef = React.useRef(false);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
};
I used the styled-components module to make the component's css (DrImageStyl ):
import React from 'react';
import styled, { css } from 'styled-components/native';
interface Sizes {
width?: number;
height?: number;
}
interface DrImageStylProps {
sizes: Sizes;
}
export const DrImageStyl = styled.Image<DrImageStylProps>`
${({ sizes }) => {
const { width, height } = sizes;
return css`
${width ? `width: ${width}px;` : ''}
${height ? `height: ${height}px;` : ''}
`;
}}
`;
I separated the code that calculates the other image size (getImageSizes):
import { Image } from 'react-native';
interface GetImageSizesParams {
uri: string;
height?: number;
width?: number;
}
export function getImageSizes({
height: heightParam,
width: widthParam,
uri,
}: GetImageSizesParams): Promise<{
width: number;
height: number;
}> {
return new Promise((resolve, reject) => {
function onSuccess(width: number, height: number) {
let widthResolve: number | undefined;
let heightResolve: number | undefined;
if (widthParam && !heightParam) {
widthResolve = widthParam;
heightResolve = height * (widthParam / width);
} else if (!widthParam && heightParam) {
widthResolve = width * (heightParam / height);
heightResolve = heightParam;
} else {
widthResolve = widthParam;
heightResolve = heightParam;
}
resolve({
width: widthResolve as number,
height: heightResolve as number,
});
}
function onError(error: any) {
reject(error);
}
try {
Image.getSize(uri, onSuccess, onError);
} catch (error) {
console.log('error', error);
}
});
}