Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Image Caching #15

Merged

Conversation

kidroca
Copy link

@kidroca kidroca commented Mar 4, 2023

Description

This PR fixes some subtle inconsistencies around image caching and rendering the same image multiple times

Related to:

Test Strategy

Use App's PR to test the changes (Expensify/App#15663)

  1. Log into an account on New Expensify
  2. Navigate to a chat where you were the last person to send a message
  3. Send a message in that chat
  4. Confirm that the avatar on the left hand side of the message that you just sent does not flickers.

Why No Unit Tests

Couldn't easily add unit tests for this change, because the main change is to the ImageLoader.load which doesn't have a test suit, and is mocked in the Image tests

Existing tests cover the changes in the Image component

kidroca added 2 commits March 4, 2023 19:59
If the Image component is rendered with a `null` source, and consecutively
updated with actual source url that was already loaded, it would fail to
pic kup the change - `state` would be `IDLE` for a brief moment and
this would cause a small flicker when the image renders

Let's always start from IDLE state, and update `shouldDisplaySource`
condition to be based on `ImageLoader.has` cache or not
Copy link
Author

@kidroca kidroca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some notes to aid the reviewer

const [state, updateState] = React.useState(() => {
const uri = resolveAssetUri(source);
if (uri != null) {
const isLoaded = ImageLoader.has(uri);
if (isLoaded) {
return LOADED;
}
}
return IDLE;
});

const [state, updateState] = React.useState(IDLE);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a state initializer, it runs only once, because of that the component does not behave correctly when we switch source, cases like

  • component is mounted with null source then changed to actual source that was already loaded - state was already initialized and is not updated to LOADED
  • component changes source dynamically to some other source that was loaded - same problem as above

This causes flickers on some occasions

Comment on lines +214 to +225
const [state, updateState] = React.useState(IDLE);
const [layout, updateLayout] = React.useState({});
const hasTextAncestor = React.useContext(TextAncestorContext);
const hiddenImageRef = React.useRef(null);
const filterRef = React.useRef(_filterId++);
const requestRef = React.useRef(null);
const uri = resolveAssetUri(source);
const isCached = uri != null && ImageLoader.has(uri);
const shouldDisplaySource =
state === LOADED || (state === LOADING && defaultSource == null);
state === LOADED ||
isCached ||
(state === LOADING && defaultSource == null);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can capture the old "state initializer" in a hook and run it on source change, so that if already loaded source is passed we can directly switch to LOADED state, but I find it simpler to always start from IDLE state and capture isCached like the proposed change. This way we've evaluated the correct result on the same render, while an use effect would only update state on the next render

Comment on lines +77 to +83
clear(requestId: number) {
const image = requests[`${requestId}`];
if (image) {
image.onerror = null;
image.onload = null;
image = null;
ImageUriCache.remove(image.src);
image.src = '';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed this to clear to represent more closely that it's not just for aborting but actually a necessary cleanup, as it also updates the ImageUriCache now

Existing functionality is unaffected (besides the rename)

@mountiny
Copy link

mountiny commented Mar 9, 2023

Requesting @marcaaron and @Beamanator for review if you have time given the comment from Kidroca about previous PR in this library

@marcaaron marcaaron removed their request for review March 9, 2023 19:36
@marcaaron
Copy link

Sorry, I've got to focus on some other things atm so unassigning myself as a reviewer.

@kidroca
Copy link
Author

kidroca commented Mar 21, 2023

Bump @mountiny @Beamanator
Some tickets are blocked by this PR

We're introducing a small change here so it should be easy to review
(There are PR comments regarding the changes)

@Beamanator
Copy link

Sorry @kidroca @mountiny I'm not working much this week so won't be able to review, but can try to get to this next week 🙃

@mountiny mountiny self-requested a review March 21, 2023 11:54
Copy link

@mountiny mountiny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kidroca one quesion

@mountiny
Copy link

mountiny commented Mar 21, 2023

@kidroca just to confirm, have you tested this in the App too? do you have a screen recording? We should test it for sure before going ahead

Oh I see its being pointed to the compiled code in this PR so I will go ahead and merge this

Copy link

@mountiny mountiny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests well in the linked App PR, lets do this Expensify/App#15663

@mountiny mountiny merged commit 7ceed18 into Expensify:master Mar 21, 2023
@kidroca
Copy link
Author

kidroca commented Mar 21, 2023

Yep, i confirm the following

  • tested compiled code against App (videos available in App's PR)
  • opened the same PR against the mainstream repo (there's also a linked issue with a generic bug reproduction sample)

Thanks for the review @mountiny 🫶

@kidroca kidroca deleted the kidroca/fix/image-caching-expensify branch March 21, 2023 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants