From 1f20c691104d012554e2dfc3cdbb5cc134536349 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 9 Jan 2025 19:50:04 +0800 Subject: [PATCH 1/6] fix(use-image): load image after props change --- packages/hooks/use-image/src/index.ts | 55 +++++++++++++-------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/hooks/use-image/src/index.ts b/packages/hooks/use-image/src/index.ts index dd47fd99a7..6814013769 100644 --- a/packages/hooks/use-image/src/index.ts +++ b/packages/hooks/use-image/src/index.ts @@ -4,7 +4,7 @@ import type {ImgHTMLAttributes, SyntheticEvent} from "react"; -import {useRef, useState, useEffect, MutableRefObject} from "react"; +import {useRef, useState, useEffect} from "react"; import {useIsHydrated} from "@nextui-org/react-utils"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; @@ -96,11 +96,33 @@ export function useImage(props: UseImageProps = {}) { } }; + const load = (): Status => { + const {loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props; + + if (!src) return "pending"; + if (ignoreFallback) return "loaded"; + + const img = new Image(); + + img.src = src; + if (crossOrigin) img.crossOrigin = crossOrigin; + if (srcSet) img.srcset = srcSet; + if (sizes) img.sizes = sizes; + if (loading) img.loading = loading; + + imageRef.current = img; + if (img.complete && img.naturalWidth) { + return "loaded"; + } + + return "loading"; + }; + useSafeLayoutEffect(() => { - if (isHydrated) { - setStatus(setImageAndGetInitialStatus(props, imageRef)); + if (isHydrated && status !== "loaded") { + setStatus(load()); } - }, [isHydrated]); + }, [isHydrated, props]); /** * If user opts out of the fallback/placeholder @@ -109,31 +131,6 @@ export function useImage(props: UseImageProps = {}) { return ignoreFallback ? "loaded" : status; } -function setImageAndGetInitialStatus( - props: UseImageProps, - imageRef: MutableRefObject, -): Status { - const {loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props; - - if (!src) return "pending"; - if (ignoreFallback) return "loaded"; - - const img = new Image(); - - img.src = src; - if (crossOrigin) img.crossOrigin = crossOrigin; - if (srcSet) img.srcset = srcSet; - if (sizes) img.sizes = sizes; - if (loading) img.loading = loading; - - imageRef.current = img; - if (img.complete && img.naturalWidth) { - return "loaded"; - } - - return "loading"; -} - export const shouldShowFallbackImage = (status: Status, fallbackStrategy: FallbackStrategy) => (status !== "loaded" && fallbackStrategy === "beforeLoadOrError") || (status === "failed" && fallbackStrategy === "onError"); From 6c1751bfe0fb7538da3955f62073c03640c4b789 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 9 Jan 2025 19:50:49 +0800 Subject: [PATCH 2/6] chore(changeset): add changeset --- .changeset/light-peaches-reflect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/light-peaches-reflect.md diff --git a/.changeset/light-peaches-reflect.md b/.changeset/light-peaches-reflect.md new file mode 100644 index 0000000000..709724d1b7 --- /dev/null +++ b/.changeset/light-peaches-reflect.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/use-image": patch +--- + +fix loading image after props changes (#4518) From 2972923e80ab57772aa81478d2d9d1c97679025f Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 9 Jan 2025 19:58:45 +0800 Subject: [PATCH 3/6] refactor(use-image): remove unused props --- packages/hooks/use-image/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/use-image/src/index.ts b/packages/hooks/use-image/src/index.ts index 6814013769..c1543dd59f 100644 --- a/packages/hooks/use-image/src/index.ts +++ b/packages/hooks/use-image/src/index.ts @@ -122,7 +122,7 @@ export function useImage(props: UseImageProps = {}) { if (isHydrated && status !== "loaded") { setStatus(load()); } - }, [isHydrated, props]); + }, [isHydrated]); /** * If user opts out of the fallback/placeholder From 680a11784ce5c51ea1d0786cf031cad1949471c4 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Thu, 9 Jan 2025 23:50:09 +0800 Subject: [PATCH 4/6] feat(use-image): add test case --- .../hooks/use-image/__tests__/use-image.test.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/hooks/use-image/__tests__/use-image.test.tsx b/packages/hooks/use-image/__tests__/use-image.test.tsx index 043fbd2507..f53db20eb9 100644 --- a/packages/hooks/use-image/__tests__/use-image.test.tsx +++ b/packages/hooks/use-image/__tests__/use-image.test.tsx @@ -27,6 +27,19 @@ describe("use-image hook", () => { await waitFor(() => expect(result.current).toBe("loaded")); }); + it("can handle changing image", async () => { + const {result, rerender} = renderHook(() => useImage({src: undefined})); + + expect(result.current).toEqual("pending"); + + setTimeout(() => { + rerender({src: "/test.png"}); + }, 3000); + + mockImage.simulate("loaded"); + await waitFor(() => expect(result.current).toBe("loaded")); + }); + it("can handle error image", async () => { mockImage.simulate("error"); const {result} = renderHook(() => useImage({src: "/test.png"})); From c72c1b2c40083f72393db8f1b2029248af9e9b32 Mon Sep 17 00:00:00 2001 From: WK Wong Date: Fri, 10 Jan 2025 00:18:41 +0800 Subject: [PATCH 5/6] fix(use-image): apply useCallback to load & remove status check --- packages/hooks/use-image/src/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/hooks/use-image/src/index.ts b/packages/hooks/use-image/src/index.ts index c1543dd59f..3960f9af94 100644 --- a/packages/hooks/use-image/src/index.ts +++ b/packages/hooks/use-image/src/index.ts @@ -4,7 +4,7 @@ import type {ImgHTMLAttributes, SyntheticEvent} from "react"; -import {useRef, useState, useEffect} from "react"; +import {useRef, useState, useEffect, useCallback} from "react"; import {useIsHydrated} from "@nextui-org/react-utils"; import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; @@ -66,7 +66,7 @@ type ImageEvent = SyntheticEvent; */ export function useImage(props: UseImageProps = {}) { - const {onLoad, onError, ignoreFallback} = props; + const {onLoad, onError, ignoreFallback, src, crossOrigin, srcSet, sizes, loading} = props; const isHydrated = useIsHydrated(); @@ -96,9 +96,7 @@ export function useImage(props: UseImageProps = {}) { } }; - const load = (): Status => { - const {loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props; - + const load = useCallback((): Status => { if (!src) return "pending"; if (ignoreFallback) return "loaded"; @@ -116,13 +114,13 @@ export function useImage(props: UseImageProps = {}) { } return "loading"; - }; + }, [src, crossOrigin, srcSet, sizes, onLoad, onError, loading]); useSafeLayoutEffect(() => { - if (isHydrated && status !== "loaded") { + if (isHydrated) { setStatus(load()); } - }, [isHydrated]); + }, [isHydrated, load]); /** * If user opts out of the fallback/placeholder From af490cf7bd435481fd2f26a18f37606305ca2774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=D3=84=D5=A1?= Date: Sat, 18 Jan 2025 21:03:49 +0800 Subject: [PATCH 6/6] chore(changeset): update package name --- .changeset/light-peaches-reflect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/light-peaches-reflect.md b/.changeset/light-peaches-reflect.md index 709724d1b7..d708781832 100644 --- a/.changeset/light-peaches-reflect.md +++ b/.changeset/light-peaches-reflect.md @@ -1,5 +1,5 @@ --- -"@nextui-org/use-image": patch +"@heroui/use-image": patch --- fix loading image after props changes (#4518)