likashefqet/react-native-image-zoom

Loading indicator "flashes" up when an image has already been loaded

mderrick opened this issue · 2 comments

Describe the bug
The loading indicator renders always, causing it to flash and disappear quickly when an image has already been downloaded because const [isLoading, setIsLoading] = useState(true); is set to true on mount and immediately set to false in onLoadEnd.

To Reproduce
Steps to reproduce the behavior:

  1. Render the ImageZoom component
  2. Unmount it and then re-render it
  3. The loading spinner will quickly flash, even though the image is resolved immediately.

Expected behavior
I'd expect the loader to not show at all as the image has been already loaded once.

Smartphone (please complete the following information):

  • iOS iPhone 13

Additional context

Something like this seems to work. Whilst just an idea, maybe there are other better ways to determine whether the loading state should be initially set to true instead of just on mount?

  const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
  ...
  const onImageLoadStart = () => {
    // Defer setting the loading state to true slightly to give
    // onLoadEnd time to set the state to false if it already loaded.
    setTimeout(() => {
      setIsLoading((prev) => {
        // If not yet set to a value, we know for sure
        // the image has not loaded, so set loading
        // state to true.
        if (prev === undefined) {
          return true
        }
        return prev
      });
    }, 0)
  };
  ...
  <AnimatedImage onLoadStart={onImageLoadStart} />

I'm going to close this issue as it was only apparent on my simulator because it's slower at rendering. A real device is fast enough you don't see the loading spinner briefly. I think the fix I mentioned above could be worth considering however to avoid that single render where the state isn't right.

I had the same issue even on a real device so i just created set of URLs that have already loaded like so:

const haveLoadedURLs = new Set<string>();

export function Image(props: Props) {
  const uri = typeof props.source === "string" ? props.source : props.source?.uri;

  // initial value based on whether the URL exists in the set
  const [loading, setLoading] = useState(!haveLoadedURLs.has(uri));
  const [error, setError] = useState(false);

  return (
    <View style={props.style}>
      {loading && !error && (
        <View style={styles.loaderContainer}>
          <ActivityIndicator color="white" size="large" style={styles.loader} />
        </View>
      )}
      {error && <Text style={{ color: "red" }}>Error</Text>}
      <ExpoImage
        onLoad={() => {
         // add the URL to the set on successful load
          haveLoadedURLs.add(uri);
          setLoading(false);
        }}
        onError={() => {
          setError(true);
          setLoading(false);
        }}
        onLoadEnd={() => setLoading(false)}
        {...props}
        style={[props.style, { opacity: loading ? 0 : 1, zIndex: 1 }]}
      />
    </View>
  );
}

Do note that this is only a workaround and not a fix.