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

feat(image): add loading src to display loading state #4783

Open
wants to merge 7 commits into
base: canary
Choose a base branch
from

Conversation

Arian94
Copy link

@Arian94 Arian94 commented Feb 2, 2025

Recreates the #3664 PR (fixes #3640).

Summary by CodeRabbit

  • New Features

    • Enhanced image loading experience by introducing a customizable loading state that displays a placeholder image while the main image loads.
    • Added a new App component to demonstrate custom loading images.
    • Introduced new stories in Storybook to showcase various loading and fallback scenarios.
  • Documentation

    • Added new documentation covering the custom loading image functionality.
    • Revised descriptions for image properties to provide clearer guidance on loading and fallback behavior.

@Arian94 Arian94 requested a review from jrgarciadev as a code owner February 2, 2025 07:07
Copy link

changeset-bot bot commented Feb 2, 2025

⚠️ No Changeset found

Latest commit: ef99401

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Feb 2, 2025

Someone is attempting to deploy a commit to the HeroUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Feb 2, 2025

Walkthrough

This update introduces a new React component and supporting files to differentiate between the loading and error states of an image. A new property, loadingSrc, is added to both the Image component and the related hook to enable displaying a custom loading image. Existing logic, documentation, and Storybook stories have been updated accordingly to reflect these changes.

Changes

Files Change Summary
apps/docs/content/components/image/customLoading.raw.jsx
apps/docs/content/components/image/customLoading.ts
Introduced a new React component (App) for demonstrating the custom loading feature and exported it via a mapping file.
apps/docs/content/docs/components/image.mdx Added a new "Custom Loading Image" section, documented the loadingSrc prop, and updated the description of fallbackSrc to reflect its error handling role.
packages/components/image/src/image.tsx
packages/components/image/src/use-image.ts
Added the loadingSrc property to the Image component and its hook. Updated the control flow to conditionally render a loading state when loadingSrc is provided, alongside error handling via fallbackSrc.
packages/components/image/stories/image.stories.tsx Updated Storybook stories: renamed existing stories and introduced new ones to demonstrate various loading and error scenarios using the new loadingSrc and updated fallbackSrc.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant IC as Image Component
    participant UH as useImage Hook
    participant N as Network

    U->>IC: Request Image (with loadingSrc & fallbackSrc)
    IC->>UH: Pass props for processing
    UH->>N: Attempt to load main image
    alt Image is still loading
        UH-->>IC: Return loading state (display loadingSrc)
    else Image loads successfully
        UH-->>IC: Return main image for display
    else Image fails to load
        UH-->>IC: Return error state (display fallbackSrc)
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Separate visual handling for image loading and error states (#3640)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
apps/docs/content/components/image/customLoading.raw.jsx (1)

8-9: Consider using local assets for demo reliability.

The demo uses external services for both the placeholder and the delayed image:

  • via.placeholder.com for loading state
  • requestly.io for simulating delay

This could make the demo unreliable if these services are down.

Consider using local assets hosted within the project for both the placeholder and the main image. For delay simulation, consider using a local development proxy or built-in delay mechanism.

packages/components/image/stories/image.stories.tsx (2)

125-125: Consider more reliable delay simulation method.

Multiple stories use requestly.io for delay simulation which could make the stories unreliable in offline environments or if the service is down.

Consider using React Suspense or a local delay mechanism for more reliable story demonstrations.

Also applies to: 146-147


165-177: Add story description for CustomLoadingAndFallback.

The new story demonstrates an important use case but lacks a description of what it's showcasing.

Add a JSDoc comment explaining the purpose of this story and when to use both loadingSrc and fallbackSrc together.

packages/components/image/src/use-image.ts (1)

27-30: Enhance loadingSrc prop documentation.

The current documentation is minimal. Consider adding:

  • Usage example
  • Default value
  • Note about interaction with disableSkeleton
 /**
  * A loading image.
+ * @example
+ * ```jsx
+ * <Image loadingSrc="/loading.gif" src="/main.jpg" />
+ * ```
+ * @default undefined
+ * @note When provided, disables the default skeleton loader
  */
apps/docs/content/docs/components/image.mdx (1)

165-169: API Enhancement: loadingSrc Prop Entry
The addition of the loadingSrc prop within the API table is well-documented. Its type, description, and default value are clearly stated, providing comprehensive guidance for developers on how to use this new feature. Consider verifying whether the default value ("-") accurately represents the intended default state (e.g., an empty string might be more conventional) if applicable.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92281a6 and ba8eeb3.

📒 Files selected for processing (6)
  • apps/docs/content/components/image/customLoading.raw.jsx (1 hunks)
  • apps/docs/content/components/image/customLoading.ts (1 hunks)
  • apps/docs/content/docs/components/image.mdx (2 hunks)
  • packages/components/image/src/image.tsx (2 hunks)
  • packages/components/image/src/use-image.ts (7 hunks)
  • packages/components/image/stories/image.stories.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/docs/content/components/image/customLoading.ts
🔇 Additional comments (4)
packages/components/image/src/image.tsx (1)

16-16: LGTM! Clean integration of loadingSrc.

The loadingSrc prop is well-integrated into the existing component structure, maintaining consistency with the current implementation pattern.

Also applies to: 49-49

packages/components/image/src/use-image.ts (1)

140-142: LGTM! Clear separation of loading states.

The loading state logic is well-organized with clear separation between:

  • Loading state with custom image
  • Error state with fallback
  • Default skeleton loader
apps/docs/content/docs/components/image.mdx (2)

66-73: Documentation Clarity: "Custom Loading Image" Section
The new section clearly explains the purpose of the loadingSrc prop and how it allows a custom loading image to be displayed while the main image is still loading. The use of a dedicated code demo and a note about the simulated network delay is consistent with other sections in the document.


172-176: Improved Clarity: fallbackSrc Prop Description Update
The updated description for the fallbackSrc prop now specifies that it is used when the main image fails to load or encounters an error, which is clearer than the previous explanation. This update helps avoid ambiguity about the prop’s behavior.

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

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

please include tests

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/docs/content/docs/components/image.mdx (1)

57-60: Add "the" before "Image component" for better readability.

The sentence should read "The Image component has a built-in skeleton animation..."

-Image component has a built-in `skeleton` animation to indicate the image is loading in case the `loadingSrc` is not defined.
+The Image component has a built-in `skeleton` animation to indicate the image is loading in case the `loadingSrc` is not defined.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~58-~58: You might be missing the article “the” here.
Context: ...d} /> ### Animated (Skeleton) Loading Image component has a built-in skeleton ani...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba8eeb3 and 33053b4.

📒 Files selected for processing (4)
  • apps/docs/content/components/image/customLoading.raw.jsx (1 hunks)
  • apps/docs/content/components/image/fallback.raw.jsx (1 hunks)
  • apps/docs/content/components/image/index.ts (2 hunks)
  • apps/docs/content/docs/components/image.mdx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/docs/content/components/image/customLoading.raw.jsx
🧰 Additional context used
🪛 LanguageTool
apps/docs/content/docs/components/image.mdx

[uncategorized] ~58-~58: You might be missing the article “the” here.
Context: ...d} /> ### Animated (Skeleton) Loading Image component has a built-in skeleton ani...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

🔇 Additional comments (5)
apps/docs/content/components/image/fallback.raw.jsx (1)

9-9: LGTM! Good example for demonstrating fallback behavior.

The intentionally invalid image source effectively demonstrates the fallback functionality.

apps/docs/content/components/image/index.ts (1)

5-5: LGTM! Clean integration of the new custom loading example.

The changes follow the existing pattern for importing and exporting examples.

Also applies to: 14-14

apps/docs/content/docs/components/image.mdx (3)

65-71: LGTM! Clear documentation for the new custom loading feature.

The new section effectively explains the loadingSrc prop with a practical example and network simulation note.


83-84: LGTM! Helpful note about combining loading and fallback states.

Good addition clarifying that both states can be handled simultaneously.


164-169: LGTM! Well-documented API properties.

The new loadingSrc prop and updated fallbackSrc description are clear and accurate.

Also applies to: 173-173

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/components/image/__tests__/image.test.tsx (3)

30-39: Enhance loading state test coverage.

The test could be improved in several ways:

  1. Add cleanup to prevent test pollution
  2. Add explicit null check instead of using non-null assertion
  3. Test the transition from loading to loaded state

Consider this improved version:

 test("renders loading source while loading the image.", async () => {
   const wrapper = render(<Image loadingSrc={loadingSrc} src={src} />);
   const imageParent = wrapper.getByRole("img").parentElement;
 
-  expect(imageParent?.getAttribute("data-testid")).toEqual("heroUI/image_parent");
+  expect(imageParent).not.toBeNull();
+  expect(imageParent!.getAttribute("data-testid")).toEqual("heroUI/image_parent");
 
   const computedStyle = window.getComputedStyle(imageParent!);
 
   expect(computedStyle.backgroundImage).toBe(`url(${loadingSrc})`);
+
+  // Test transition to loaded state
+  fireEvent.load(wrapper.getByRole("img"));
+  await waitFor(() => {
+    const updatedStyle = window.getComputedStyle(imageParent!);
+    expect(updatedStyle.backgroundImage).toBe(`url(${src})`);
+  });
+
+  // Cleanup
+  wrapper.unmount();
 });

41-55: Optimize error handling test.

The test has some potential improvements:

  1. The 6-second timeout seems excessive for a unit test
  2. Missing cleanup
  3. Could use more specific error simulation

Consider this improved version:

 test("renders fallback source if src is wrong or not found.", async () => {
   const onError = jest.fn();
   const wrapper = render(
     <Image alt="test" fallbackSrc={fallbackSrc} src="wrong-src-address" onError={onError} />,
   );
   const imageParent = wrapper.getByRole("img").parentElement;
 
-  fireEvent.error(wrapper.getByRole("img"));
+  // Simulate specific error type
+  fireEvent.error(wrapper.getByRole("img"), {
+    target: { error: new Error('Failed to load image') }
+  });
 
-  await waitFor(() => expect(onError).toHaveBeenCalled(), {timeout: 5_000});
+  await waitFor(() => expect(onError).toHaveBeenCalled(), {timeout: 1000});
   expect(imageParent?.getAttribute("data-testid")).toEqual("heroUI/image_parent");
 
   const computedStyle = window.getComputedStyle(imageParent!);
 
   expect(computedStyle.backgroundImage).toBe(`url(${fallbackSrc})`);
-}, 6_000);
+
+  // Cleanup
+  wrapper.unmount();
+}, 2000);

57-61: Add cleanup to prevent test pollution.

The test is well-structured but should include cleanup to prevent potential test pollution.

 test("renders image if there is no loading or fallback behavior defined", async () => {
   const wrapper = render(<Image src={src} />);
 
   expect(wrapper.getByRole("img")).toHaveAttribute("src", src);
+  wrapper.unmount();
 });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33053b4 and d763e24.

📒 Files selected for processing (2)
  • packages/components/image/__tests__/image.test.tsx (2 hunks)
  • packages/components/image/src/image.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/components/image/src/image.tsx
🔇 Additional comments (1)
packages/components/image/__tests__/image.test.tsx (1)

8-8: Consider using an absolute URL for loadingSrc.

Using a relative path /images/local-image-small.jpg might cause issues in different test environments. Consider using an absolute URL (like the placeholder URLs used for src and fallbackSrc) to ensure consistent test behavior across environments.

@Arian94
Copy link
Author

Arian94 commented Feb 4, 2025

please include tests

I couldn't. The second test needs the onError handler to get called but it doesn't. Don't know how to make it work.

@wingkwong
Copy link
Member

you may take a look at packages/hooks/use-image/__tests__/use-image.test.tsx

@Arian94
Copy link
Author

Arian94 commented Feb 8, 2025

you may take a look at packages/hooks/use-image/__tests__/use-image.test.tsx

done.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d763e24 and de08d98.

📒 Files selected for processing (1)
  • packages/components/image/__tests__/image.test.tsx (4 hunks)
🔇 Additional comments (3)
packages/components/image/__tests__/image.test.tsx (3)

50-71: LGTM! Well-structured test for loading state.

The test thoroughly verifies both the loading state and the final loaded state, including:

  • Loading image display
  • onLoad callback
  • Proper cleanup

110-115: LGTM! Clear test description update.

The test description now accurately reflects the expanded scope including loading behavior.


33-33: LGTM! Consistent cleanup practice.

Good addition of wrapper.unmount() calls across all test cases to prevent memory leaks and test interference.

Also applies to: 47-47, 70-70, 107-107, 114-114, 121-121, 129-129, 143-143, 164-164

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/components/image/__tests__/image.test.tsx (2)

30-38: Optimize cleanup code performance.

The cleanup code is good but can be optimized by avoiding the delete operator.

   afterAll(() => {
-    delete window.Image.prototype._onload;
+    window.Image.prototype._onload = undefined;
     Object.defineProperty(window.Image.prototype, "onload", {
       value: null,
       writable: true,
       configurable: true,
     });
   });
🧰 Tools
🪛 Biome (1.9.4)

[error] 32-32: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


84-130: Enhance error handling test.

The test case is good but can be improved in two areas:

  1. Performance optimization for cleanup
  2. More robust error simulation
   const cleanup = () => {
-    delete window.Image.prototype._onerror;
+    window.Image.prototype._onerror = undefined;
     Object.defineProperty(window.Image.prototype, "onerror", {
       value: null,
       writable: true,
       configurable: true,
     });
   };

   act(() => {
-    imageOnerror();
+    imageOnerror(new Event('error'));
   });
🧰 Tools
🪛 Biome (1.9.4)

[error] 103-103: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de08d98 and ef99401.

📒 Files selected for processing (1)
  • packages/components/image/__tests__/image.test.tsx (4 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/components/image/__tests__/image.test.tsx

[error] 32-32: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 103-103: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (4)
packages/components/image/__tests__/image.test.tsx (4)

7-8: LGTM! Constants are well-defined and descriptive.

The constants are appropriately named and their values are clear and meaningful.


13-28: LGTM! Image onload tracking setup is well-implemented.

The setup code properly tracks the image onload event by modifying the Image prototype, which is essential for testing loading states.


61-82: LGTM! Loading state test is comprehensive.

The test case thoroughly verifies:

  • Initial loading image display
  • Proper loading state transition
  • onLoad callback invocation
  • Cleanup after test completion

132-137: LGTM! Test cases have proper cleanup.

All test cases now include proper cleanup by unmounting components after test completion.

Also applies to: 139-187

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request] Make a Distinction Between Loading and Error States in Image Component
2 participants