javascriptreactjsreact-nativesupabasereact-native-paper

images in React Native not rendering simultaneously


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!


Solution

  • 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