[HOLD for payment 2023-04-27] [$1000] The attachment viewer loads every image again when switching with arrows
If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!
Action Performed:
- Open any report with more than one image sent
- Click on any one image
- Click on an arrow to switch image
Expected Result
The spinner should only display once when the image loads, after that, the image should not need to show the spinner and be shown instantly
Actual Result
The spinner is shown on every image when switching and images are loaded again every time.
Workaround:
unknown
Platforms:
Which of our officially supported platforms is this issue occurring on?
- [x] Android / native
- [x] Android / Chrome
- [x] iOS / native
- [x] iOS / Safari
- [x] MacOS / Chrome / Safari
- [x] MacOS / Desktop
Version Number: 1.2.83-1 Reproducible in staging?: y Reproducible in production?: y If this was caught during regression testing, add the test name, ID and link from TestRail: Email or phone of affected tester (no customers): Logs: https://stackoverflow.com/c/expensify/questions/4856 Notes/Photos/Videos:
https://user-images.githubusercontent.com/43996225/224829096-7835d17f-58a9-4d71-b04b-db2b30a79cdd.mov
https://user-images.githubusercontent.com/43996225/224829210-cfb2f08f-2543-4d2e-bcc3-66e5f90da9d6.mp4
Expensify/Expensify Issue URL: Issue reported by: @esh-g Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1678613459293019 View all open jobs on GitHub
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~0136002576e7090b5b
- Upwork Job ID: 1636029895829024768
- Last Price Increase: 2023-03-15
Triggered auto assignment to @isabelastisser (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.
Bug0 Triage Checklist (Main S/O)
- [x] This "bug" occurs on a supported platform (ensure
Platformsin OP are ✅) - [x] This bug is not a duplicate report (check E/App issues and #expensify-bugs)
- If it is, comment with a link to the original report, close the issue and add any novel details to the original issue instead
- [x] This bug is reproducible using the reproduction steps in the OP. S/O
- If the reproduction steps are clear and you're unable to reproduce the bug, check with the reporter and QA first, then close the issue.
- If the reproduction steps aren't clear and you determine the correct steps, please update the OP.
- [x] This issue is filled out as thoroughly and clearly as possible
- Pay special attention to the title, results, platforms where the bug occurs, and if the bug happens on staging/production.
- [x] I have reviewed and subscribed to the linked Slack conversation to ensure Slack/Github stay in sync
Job added to Upwork: https://www.upwork.com/jobs/~0136002576e7090b5b
Current assignee @isabelastisser is eligible for the External assigner, not assigning anyone new.
Triggered auto assignment to Contributor-plus team member for initial proposal review - @thesahindia (External)
Triggered auto assignment to @johnmlee101 (External), see https://stackoverflow.com/c/expensify/questions/7972 for more details.
Hey @isabelastisser do you mind completing the checklist https://github.com/Expensify/App/issues/15922#issuecomment-1466937899
sorry, forgot to check the boxes before applying the external label!
I think this is a dupe of https://github.com/Expensify/App/issues/6527 which was deferred for https://github.com/Expensify/App/issues/12603 which ultimately deals with caching these images
@johnmlee101 Are you sure this is a dupe of #6527 ? Because I think that issue was even before the carousel control was implemented and was about caching the images to the browser so they stay even after reload. But here, the images are already loaded and we can have them switch without caching them to the browser.
Proposal
Please re-state the problem that we are trying to solve in this issue.
The spinner keeps shown on every image when switching even it loaded before.
What is the root cause of that problem?
Because in the componentDidUpdate lifecycle of component ImageView, it call method to set isLoading=true everytime the url of is changed (it didn't care if the url is passed before or the image is already loaded in cache)
https://github.com/Expensify/App/blob/eedd92a2547a18dc60a810c9f868bf5b3ba77681/src/components/ImageView/index.js#L63-L69
when isLoading state equal true, it shows spinner https://github.com/Expensify/App/blob/eedd92a2547a18dc60a810c9f868bf5b3ba77681/src/components/ImageView/index.js#L298-L302
What changes do you think we should make in order to solve the problem?
I think we should apply delayed spinner pattern in this case. According to some UX tips, we should only show spinner if something is loaded > 1s, otherwise it will distract user.
More details, I think about add an additional props delayTime to component FullScreenLoadingIndicator, and update current FullScreenLoadingIndicator to something like this:
import React, { useEffect, useState } from 'react';
const defaultProps = {
....
delayTime: 0,
};
const FullScreenLoadingIndicator = (props) => {
const [showable, setShowable] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setShowable(true), props.delayTime);
return () => clearTimeout(timer);
});
return showable && <..../>;
};
export default FullScreenLoadingIndicator;
so we can set delayTime in the loading part of component ImageView. The advantage of this pattern is we also improve UX for user when they load lightweight images or their network is very good
What alternative solutions did you explore? (Optional)
Maybe cache loaded images, but it will be a fancy work.
Proposal
I think you need to render each AttachmentView for every attachment in AttachmentCarousel and use style display: none;. Because your AttachmentView will re-render every time when current slide will be change.
You can cache image content or make Set with loaded urls, but i think it more complicated and redundant for Image component.
Plus, if you will use style, you can use opacity and it is good for animation.
Contributor details Your Expensify account email: [email protected] Upwork Profile Link: https://www.upwork.com/freelancers/~01146d8467ba0b62e4
📣 @nefedov-dm! 📣
Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork. Please follow these steps:
- Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
- Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
- Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.

Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
Proposal
Please re-state the problem that we are trying to solve in this issue.
The attachment viewer loads every image again when switching with arrows
What is the root cause of that problem?
The Image component used debouncedConfigureImageSource with 220 ms:
https://github.com/Expensify/App/blob/main/src/components/Image/index.js#L14
this.debouncedConfigureImageSource = _.debounce(this.configureImageSource, 220);
...
this.debouncedConfigureImageSource();
...
this.debouncedConfigureImageSource.cancel();
this.debouncedConfigureImageSource();
What changes do you think we should make in order to solve the problem?
It will be fixed if we use it with the immediate: true option:
this.debouncedConfigureImageSource = _.debounce(this.configureImageSource, 220, true);
Or if we use it without debounce.
It needs more investigation like why the debounce actually used to make sure that the fix won't break anything.
What alternative solutions did you explore? (Optional)
This is expected behavior, debounce here is added to ensure there is no flickering of the image while switching images fast.
cc: @chiragsalian @luacmartins @trjExpensify @thesahindia
Not overdue, still discussing!
@johnmlee101 is this a dupe or expected behavior? Let me know if I should close it.
This is expected behavior, debounce here is added to ensure there is no flickering of the image while switching images fast.
cc: @chiragsalian @luacmartins @trjExpensify @thesahindia
Image component mounted -> after 220ms -> configureImageSource -> RNImage.getSize -> onLoad -> isLoading: false
So even if the image is getting from the cache it waits for 220ms and then calls onLoad. If I'm not missing anything then I think it shouldn't be like that.
@usenbekov That's right, but it should be like that, and debounce duration is added to avoid unnecessary flickering while switching between images faster.
Moreover, we are planning to improve the Image to remove debounce for some cases and that's in planning we will get started on that next week.
@chiragsalian or @luacmartins Can you comment on this thanks!
cc: @thesahindia @johnmlee101
Hey guys I think my PR here, improves the situation a bit
- https://github.com/Expensify/App/pull/15663
It fixes a workaround we had with image flickering for avatars, by fixing react-native-web bug where internal image cache was ignored (delaying the displaying of an already loaded image a bit)
The PR also makes the debounce here unnecessary - we no longer have a flicker between prev/next image
https://github.com/Expensify/App/blob/0b24114bfd99f92e24f849e46a7ed2622adbee7c/src/components/Image/index.js#L14
Check this out:
https://user-images.githubusercontent.com/12156624/226054739-1c464e09-efb4-4a92-8064-737c5cb63f70.mov
Yeah, the spinner still shows briefly, so I like the idea of waiting a bit before rendering it (~BTW this is DEV, so it's possible the spinner would not show at all on a PROD build~: still appears on PROD)
The Carousel strategy might be improved a bit if we include image preloading (This would also fix the current problem with showing the spinner for cached images)
- When we open an attachment / carousel (modal)
- An image is starts loading (we see a spinner then the image)
- After the image loads we
Image.preloadthe image before/after (e.g. the ones that would be shown when we press an arrow) - Since
Image.preloadreturns a promise we can track whether it's still loading or not (images from cache would preload instantly) - When the actual arrow is pressed - we only show loading if the image is still (pre)loading - otherwise we don't show a spinner but directly display the image
@kidroca I couldn't find Image.preload method in RN docs, is it your custom method?
Thanks guys! Let's hold this issue.
cc: @johnmlee101
@kidroca I couldn't find
Image.preloadmethod in RN docs, is it your custommethod?
Sorry the name is actually prefetch, it's this one https://reactnative.dev/docs/image#prefetch
@kidroca yay, I'm not really sure how you will implement it, but caching is one of hardest problems. If we're going to implement an array or something to mark fetched status of an image url, it would take effort. I prefer let this hardest thing to the native system handle it. The delay spinner is good enough for our case.
@Santhosh-Sellavel WRT this, yes we plan to improve the Image component by removing the debounce. Not sure if that will get rid of the loading issue though.
Yes, I agree!
@Santhosh-Sellavel so will this be resolved in that issue?
It's best to hold until #15663 is merged
Not overdue, still on hold!