react-native-web icon indicating copy to clipboard operation
react-native-web copied to clipboard

Image: ignore changes to 'onLoad' callback if image already loaded

Open oste opened this issue 4 years ago • 8 comments

The problem The Image component reloads on each rerender.

How to reproduce https://codesandbox.io/s/happy-mountain-i2eol?file=/src/App.js

Steps to reproduce:

  1. Add the Image tag to your component
  2. Add the onLoad property to the Image tag
  3. set state in the onLoad property

Expected behavior It would work similar to the img tag. Here is the same code sandbox with <Image swapped out for <img src= https://codesandbox.io/s/happy-bell-i4v5q?file=/src/App.js

Environment (include versions). Did this work in previous versions? Please see code sandbox. It did not work differently in a previous verion that I am aware of.

Disclaimer: My expectations and use case might not align with react native web. However I am not sure how to successfully set state in onLoad without a duplicate render.

oste avatar May 17 '21 17:05 oste

Your code is creating a new onLoad function every time the onLoad function is called because the loadedCount dependency changes every time. This is an infinite loop you bail out of using the count condition.

You can reproduce the issue for img by setting the callback to ref instead of onLoad (which the browser only calls once): https://codesandbox.io/s/nifty-dewdney-vi8pe?file=/src/App.js

Arguably RNWeb should not call new onLoad functions if the image has already loaded, so I'll leave this open. But you should fix the loop in your code that is creating new functions on every state update.

necolas avatar May 17 '21 20:05 necolas

thanks for the information @necolas.

Unfortunately in my app removing dependencies from the useCallback doesn't help. Even if I just use a plain old function like this it still renders twice.

  const [isImageLoading, setIsImageLoading] = useState(true);

  const onLoad = () => {
    setIsImageLoading(false);
  };
  
      <Image
        onLoad={onLoad}
        source={{
          uri:...  

oste avatar May 17 '21 22:05 oste

You're still creating a new onLoad function each time the state changes

necolas avatar May 17 '21 22:05 necolas

ok moving the Image to its own component and passing onLoad as a prop seems to work. Thanks again. Looking forward to following this trough. I will be on standby for testing or anything else you might need don't hesitate to ask.

oste avatar May 17 '21 23:05 oste

I picked this back up and realized my initial solution was not working as intended due to commented-out code. I am not sure how to set the loading state without causing a duplicate image load. I have tried moving the onLoad function into both the parent and child components with no luck.

@necolas Can you provide more detail on avoiding creating a new onLoad function every time the onLoad function is called. Is this possible while using setState in a functional component?

oste avatar May 18 '21 04:05 oste

@oste is it what you want? https://codesandbox.io/s/loving-montalcini-f3ibo?file=/src/App.js

MohammadAzimi avatar May 18 '21 09:05 MohammadAzimi

@MohammadAzimi That is precisely what I am trying to achieve. However, as I mentioned that solution doesn't work as well in my app. It actually results in an infinite loop of that same image loading. It would seem that one of these useEffect dependencies are changing but I am not sure which since I also have the uri hardcoded and the same onLoad function. https://github.com/necolas/react-native-web/blob/2d9dca42fa67d09de8ff2de323c622b47a820354/packages/react-native-web/src/exports/Image/index.js#L278

oste avatar May 18 '21 13:05 oste

@oste I forked your codesandbox link and refactored to use a class component instead of hooks. This issue did not happen. If this is an option explore this path as well.

bisvarup avatar Jun 23 '21 19:06 bisvarup