fix(use-image): load images after props change (#4523)

* fix(use-image): load image after props change

* chore(changeset): add changeset

* refactor(use-image): remove unused props

* feat(use-image): add test case

* fix(use-image): apply useCallback to load & remove status check

* chore(changeset): update package name
This commit is contained in:
աӄա 2025-01-30 20:44:38 +08:00 committed by GitHub
parent 8452603b5b
commit f9c2be4509
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 42 additions and 29 deletions

View File

@ -0,0 +1,5 @@
---
"@heroui/use-image": patch
---
fix loading image after props changes (#4518)

View File

@ -27,6 +27,19 @@ describe("use-image hook", () => {
await waitFor(() => expect(result.current).toBe("loaded")); 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 () => { it("can handle error image", async () => {
mockImage.simulate("error"); mockImage.simulate("error");
const {result} = renderHook(() => useImage({src: "/test.png"})); const {result} = renderHook(() => useImage({src: "/test.png"}));

View File

@ -4,7 +4,7 @@
import type {ImgHTMLAttributes, SyntheticEvent} from "react"; import type {ImgHTMLAttributes, SyntheticEvent} from "react";
import {useRef, useState, useEffect, MutableRefObject} from "react"; import {useRef, useState, useEffect, useCallback} from "react";
import {useIsHydrated} from "@heroui/react-utils"; import {useIsHydrated} from "@heroui/react-utils";
import {useSafeLayoutEffect} from "@heroui/use-safe-layout-effect"; import {useSafeLayoutEffect} from "@heroui/use-safe-layout-effect";
@ -66,7 +66,7 @@ type ImageEvent = SyntheticEvent<HTMLImageElement, Event>;
*/ */
export function useImage(props: UseImageProps = {}) { export function useImage(props: UseImageProps = {}) {
const {onLoad, onError, ignoreFallback} = props; const {onLoad, onError, ignoreFallback, src, crossOrigin, srcSet, sizes, loading} = props;
const isHydrated = useIsHydrated(); const isHydrated = useIsHydrated();
@ -96,25 +96,7 @@ export function useImage(props: UseImageProps = {}) {
} }
}; };
useSafeLayoutEffect(() => { const load = useCallback((): Status => {
if (isHydrated) {
setStatus(setImageAndGetInitialStatus(props, imageRef));
}
}, [isHydrated]);
/**
* If user opts out of the fallback/placeholder
* logic, let's just return 'loaded'
*/
return ignoreFallback ? "loaded" : status;
}
function setImageAndGetInitialStatus(
props: UseImageProps,
imageRef: MutableRefObject<HTMLImageElement | null | undefined>,
): Status {
const {loading, src, srcSet, crossOrigin, sizes, ignoreFallback} = props;
if (!src) return "pending"; if (!src) return "pending";
if (ignoreFallback) return "loaded"; if (ignoreFallback) return "loaded";
@ -132,6 +114,19 @@ function setImageAndGetInitialStatus(
} }
return "loading"; return "loading";
}, [src, crossOrigin, srcSet, sizes, onLoad, onError, loading]);
useSafeLayoutEffect(() => {
if (isHydrated) {
setStatus(load());
}
}, [isHydrated, load]);
/**
* If user opts out of the fallback/placeholder
* logic, let's just return 'loaded'
*/
return ignoreFallback ? "loaded" : status;
} }
export const shouldShowFallbackImage = (status: Status, fallbackStrategy: FallbackStrategy) => export const shouldShowFallbackImage = (status: Status, fallbackStrategy: FallbackStrategy) =>