-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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 #2493
base: master
Are you sure you want to change the base?
Fix Image Caching #2493
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 1929474:
|
38bb9fa
to
216e758
Compare
exports[`components/Image prop "source" is set immediately if the image has already been loaded 1`] = ` | ||
exports[`components/Image prop "source" is set immediately if the image has already been loaded 2`] = ` | ||
<div | ||
class="css-view-175oi2r r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010" | ||
> | ||
<div | ||
class="css-view-175oi2r r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw r-backgroundSize-4gszlv" | ||
style="background-image: url(https://google.com/favicon.ico);" | ||
style="background-image: url(https://twitter.com/favicon.ico);" | ||
/> | ||
<img | ||
alt="" | ||
class="css-accessibilityImage-9pa8cd" | ||
draggable="false" | ||
src="https://google.com/favicon.ico" | ||
src="https://twitter.com/favicon.ico" | ||
/> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diff is confusing, it does not represent the change correctly
- Test case name was updated
- This resulted in 1 new snapshot created and 1 obsolete snapshot removed
If we inspect the snapshot (number 2) we see that it correctly renders uriTwo
react-native-web/packages/react-native-web/src/exports/Image/__tests__/index-test.js
Line 281 in 38bb9fa
const uriTwo = 'https://twitter.com/favicon.ico'; |
packages/react-native-web/src/exports/Image/__tests__/index-test.js
Outdated
Show resolved
Hide resolved
const [state, updateState] = React.useState(() => { | ||
const uri = resolveAssetUri(source); | ||
if (uri != null) { | ||
const isLoaded = ImageLoader.has(uri); | ||
if (isLoaded) { | ||
return LOADED; | ||
} | ||
} | ||
return IDLE; | ||
}); |
There was a problem hiding this comment.
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
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); |
There was a problem hiding this comment.
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
const ImageLoader = { | ||
abort(requestId: number) { | ||
let image = requests[`${requestId}`]; | ||
clear(requestId: number) { | ||
const image = requests[`${requestId}`]; |
There was a problem hiding this comment.
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
The change doesn't alter existing behavior
BTW This PR also fixes |
Hi, please could you rebase this. Now that 0.19 is done, the 0.20 release will be focused on Image changes. |
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
216e758
to
8991b58
Compare
✅ rebased |
8991b58
to
fa50f4f
Compare
test('is not set immediately if the image has not already been loaded', () => { | ||
const uri = 'https://google.com/favicon.ico'; | ||
test('is set immediately while images is loading and there is no default source', () => { | ||
const uri = 'https://google.com/not-yet-loaded-image.ico'; | ||
const source = { uri }; | ||
const { container } = render(<Image source={source} />); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this was originally true, and source was not rendered until loaded, but if you inspect the snapshot you can see that <img />
with the source was rendered, so I renamed the test
We do render the source while loading, unless there's a default source
react-native-web/packages/react-native-web/src/exports/Image/index.js
Lines 210 to 211 in e8098fd
const shouldDisplaySource = | |
state === LOADED || (state === LOADING && defaultSource == null); |
fa50f4f
to
491b728
Compare
One unit test snapshot is failing |
As we can see by the created snapshot - the image is rendered This is because there's no default source. In such case we render the Image source while it loads
491b728
to
1929474
Compare
Sorry, should be fine now |
Description
This PR fixes some subtle inconsistencies around image caching and rendering the same image multiple times
Fixes #2492
Test Strategy
Created a sandbox sample from the pull request, where the bug can no longer happens
https://codesandbox.io/s/rnw-image-flicker-bug-fix-s93ri0
Steps:
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 theImage
testsExisting tests cover the changes in the
Image
component