I am building a React Native app that loads images in parallel from storage in Supabase, before returning them to a list. I'm using a custom hook to retrieve the images from storage in Supabase:
const fetchImages = async (recipes) => {
if (!recipes) {
return;
}
const imageNames = recipes.map(recipe => recipe ? recipe.img : undefined);
const urlPromises = imageNames.map(async (imageName) => {
if (!imageName) {
return;
}
const publicURL = supabase.storage.from('images').getPublicUrl(imageName);
return {
name: imageName,
publicURL
};
});
const urls = await Promise.all(urlPromises);
setImageURLs(urls);
};
Then, I am passing the images to a function called "renderSwipeItem", which is handled by SwipeListView:
const { imageURLs } = useFetchData();
const renderSwipeItem = ({ item, index }) => {
const imageObj = imageURLs && imageURLs.find(imageObj => imageObj.name === item.img);
const imageUrl = imageObj ? imageObj.publicURL.data.publicUrl : null;
return (
<View style={styles.container}>
<List.Item
key={index}
title={item.name} // This is where the recipe name is displayed
titleNumberOfLines={2} // Specify the number of lines
titleEllipsizeMode='tail'
left={() =>
<Image
source={{uri: imageUrl ? imageUrl : null}} // Use the signedURL if found, otherwise use null
style={styles.listImage}
/>
}
onPress={() => navigation.navigate('Recipe', {
recipe: item,
image: imageUrl ? imageUrl : null,
})}
style={styles.listItem}
/>
</View>
)
};
return (
<SwipeListView
keyExtractor={(item, index) => index.toString()}
data={images}
renderItem={renderSwipeItem}
renderHiddenItem={renderHiddenItem}
leftOpenValue={100}
rightOpenValue={-100}
disableRightSwipe={true}
disableLeftSwipe={false}
/>
);
The images load successfully, but after loading in parallel the images do not render at the same time. I attempted to resolve the issue by setting up an ActivityIndicator that runs while the images are loading:
return (
imageLoading ? <ActivityIndicator size="large" color="#0000ff" /> :
<SwipeListView
keyExtractor={(item, index) => index.toString()}
data={images}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
renderItem={renderSwipeItem}
renderHiddenItem={renderHiddenItem}
leftOpenValue={100}
rightOpenValue={-100}
disableRightSwipe={true}
disableLeftSwipe={false}
/>
);
...but after loading stops, the images are still rendering one by one. Any tips on how I can trigger a loading state while the images are rendering? Thanks!
This is because each Image
takes its own time to load.
One work around is to include ActivityIndicator
for each of the image, you can handle the status by Image
props
, when Loading Start onLoadStart
and once the image is loaded use onLoad
to show it.
<View style={styles.itemContainer}>
{loading && <ActivityIndicator style={styles.activityIndicator} />}
<Image
source={{ uri: item.imageUrl }}
style={styles.image}
onLoadStart={() => setLoading(true)}
onLoad={() => setLoading(false)}
/>
</View>
Expo Snack - link