feat: add skeleton loading state to ServerAvatar component#7384
feat: add skeleton loading state to ServerAvatar component#7384Carlos2505dev wants to merge 1 commit into
Conversation
- Replace the empty render when image is not available with a SkeletonPlaceholder - Mark image prop as optional in the IServerAvatar interface - Add Storybook stories covering image, skeleton, and theme variants - Add snapshot tests via generateSnapshots utility - Remove resolved TODO comment
WalkthroughServerAvatar component now renders a themed skeleton placeholder when the image prop is missing, replacing the previous behavior of rendering nothing. Supporting changes include new Storybook stories documenting all avatar variants and skeleton states across themes, plus snapshot tests. ChangesServerAvatar skeleton loading support
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/views/WorkspaceView/ServerAvatar.stories.tsx (1)
13-21: ⚡ Quick winUse an interface for props and explicit return types for the story components.
To match TS conventions in this repo, extract the inline props shape into an interface and annotate return types for
ThemedServerAvatarand each exported story function.As per coding guidelines:
**/*.{ts,tsx}: “Prefer interfaces over type aliases for defining object shapes in TypeScript” and “add explicit type annotations to function parameters and return types”.Also applies to: 31-51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/views/WorkspaceView/ServerAvatar.stories.tsx` around lines 13 - 21, Extract the inline props object for ThemedServerAvatar into a named interface (e.g., interface ThemedServerAvatarProps) and annotate ThemedServerAvatar with that interface as its parameter type plus an explicit React.FC or explicit return type like JSX.Element; then add explicit return type annotations (JSX.Element) to each exported story function (the story names in this file) so all component/story functions use the new interface and have explicit return types; update imports/types if needed to use TSupportedThemes in the interface.Source: Coding guidelines
app/views/WorkspaceView/ServerAvatar.tsx (1)
33-33: ⚡ Quick winAdd an explicit return type to the component render function.
Please annotate the memoized component callback return type explicitly to align with the TypeScript guideline for explicit parameter/return typing.
As per coding guidelines:
**/*.{ts,tsx}: “Use TypeScript for type safety; add explicit type annotations to function parameters and return types”.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/views/WorkspaceView/ServerAvatar.tsx` at line 33, The memoized component callback ServerAvatar lacks an explicit return type; update the declaration of the React.memo callback for ServerAvatar (the function with signature ({ url, image }: IServerAvatar) => ...) to include an explicit return type such as : JSX.Element (or : JSX.Element | null if it can return null) so the component render function is fully typed per guidelines; leave the existing IServerAvatar param annotation in place.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/views/WorkspaceView/ServerAvatar.tsx`:
- Around line 38-44: ServerAvatar currently renders Image whenever image is
truthy so the SkeletonPlaceholder only shows when image is falsy; change
ServerAvatar to track loading state (e.g., isLoading/isError) and render the
SkeletonPlaceholder while isLoading or isError is true, then render the Image
when loaded successfully. Add Image onLoad to clear isLoading and onError to set
isError (and stop loading), ensure the Image source uses `${url}/${image}` only
when image exists, and keep existing styles (styles.image, colors.strokeLight,
SIZE, BORDER_RADIUS) so the skeleton displays during slow or failed network
loads.
---
Nitpick comments:
In `@app/views/WorkspaceView/ServerAvatar.stories.tsx`:
- Around line 13-21: Extract the inline props object for ThemedServerAvatar into
a named interface (e.g., interface ThemedServerAvatarProps) and annotate
ThemedServerAvatar with that interface as its parameter type plus an explicit
React.FC or explicit return type like JSX.Element; then add explicit return type
annotations (JSX.Element) to each exported story function (the story names in
this file) so all component/story functions use the new interface and have
explicit return types; update imports/types if needed to use TSupportedThemes in
the interface.
In `@app/views/WorkspaceView/ServerAvatar.tsx`:
- Line 33: The memoized component callback ServerAvatar lacks an explicit return
type; update the declaration of the React.memo callback for ServerAvatar (the
function with signature ({ url, image }: IServerAvatar) => ...) to include an
explicit return type such as : JSX.Element (or : JSX.Element | null if it can
return null) so the component render function is fully typed per guidelines;
leave the existing IServerAvatar param annotation in place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 86187c60-e056-499c-a9fe-2e3542a413d0
📒 Files selected for processing (3)
app/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions
Files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbersUse TypeScript with strict mode enabled
Files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
**/*.{js,jsx,ts,tsx,json}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Prettier formatting with tabs, single quotes, 130 character line width, no trailing commas, and avoid arrow function parentheses
Files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Enforce ESLint rules from
@rocket.chat/eslint-configwith React, React Native, TypeScript, and Jest plugins
Files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
app/views/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Place screen components in 'app/views/' directory
Files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
🧠 Learnings (3)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.
Applied to files:
app/views/WorkspaceView/ServerAvatar.test.tsxapp/views/WorkspaceView/ServerAvatar.stories.tsxapp/views/WorkspaceView/ServerAvatar.tsx
📚 Learning: 2026-06-03T18:44:08.388Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-06-03T18:44:08.388Z
Learning: Applies to app/theme.tsx : Use theming context from 'app/theme.tsx'
Applied to files:
app/views/WorkspaceView/ServerAvatar.stories.tsx
📚 Learning: 2026-03-15T13:55:42.038Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6911
File: app/containers/markdown/Markdown.stories.tsx:104-104
Timestamp: 2026-03-15T13:55:42.038Z
Learning: In Rocket.Chat React Native, the markdown parser requires a space between the underscore wrapping italic text and a mention sigil (_ mention _ instead of _mention_). Ensure stories and tests that include italic-wrapped mentions follow this form to guarantee proper parsing. Specifically, for files like app/containers/markdown/Markdown.stories.tsx, and any test/content strings that exercise italic-mentions, use the pattern _ mention _ (with spaces) to prevent the mention from being treated as plain text. Validate any test strings or story content accordingly.
Applied to files:
app/views/WorkspaceView/ServerAvatar.stories.tsx
🔇 Additional comments (1)
app/views/WorkspaceView/ServerAvatar.test.tsx (1)
1-4: LGTM!
| {image ? ( | ||
| <Image style={[styles.image, { borderColor: colors.strokeLight }]} source={{ uri: `${url}/${image}` }} /> | ||
| ) : ( | ||
| <SkeletonPlaceholder backgroundColor={colors.surfaceNeutral} borderRadius={BORDER_RADIUS}> | ||
| <SkeletonPlaceholder.Item width={SIZE} height={SIZE} /> | ||
| </SkeletonPlaceholder> | ||
| )} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the component and relevant lines
FILE="app/views/WorkspaceView/ServerAvatar.tsx"
sed -n '1,200p' "$FILE" | nl -ba | sed -n '1,200p'
# Specifically show the referenced range (38-44) with context
sed -n '25,70p' "$FILE" | nl -ba | sed -n '1,120p'
# Search for any existing image loading/error handling in this file
rg -n "onLoad(Start|End)|onError|isImageLoading|hasImageError|SkeletonPlaceholder" "$FILE" || trueRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 121
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="app/views/WorkspaceView/ServerAvatar.tsx"
# Show file with line numbers using awk (since nl isn't available)
awk '{print NR "\t" $0}' "$FILE" | sed -n '1,200p'
# Show the referenced range (around lines 38-44) with context
awk '{print NR "\t" $0}' "$FILE" | sed -n '30,90p'
# Search for existing image loading/error handling in this file
rg -n "onLoad(Start|End)|onError|isImageLoading|hasImageError|SkeletonPlaceholder" "$FILE" || trueRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 2260
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for expo-image Image usage with load/error handlers
rg -n "from 'expo-image'|expo-image" app | head -n 50 || true
rg -n "onLoadStart|onLoadEnd|onError" app -S || true
# Search in entire repo for common expo-image event props
rg -n "onError=" -S . || true
rg -n "onLoad(Start|End)=" -S . || trueRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 50391
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect a few files that likely use expo-image load/error props
for FILE in \
"app/containers/ServerItem/index.tsx" \
"app/containers/Avatar/Avatar.tsx" \
"app/containers/RoomTypeIcon/OmnichannelRoomIcon.tsx" \
"app/containers/message/Urls.tsx" \
"app/containers/message/Components/Attachments/Image/Image.tsx" \
; do
if [ -f "$FILE" ]; then
echo "===== $FILE ====="
sed -n '1,220p' "$FILE" | awk '{print NR "\t" $0}' | sed -n '1,220p'
rg -n "onLoad(Start|End)|onError" "$FILE" || true
fi
doneRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 17844
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "import\s*\{\s*Image\s*\}\s*from\s*'expo-image'" app --glob='*.{ts,tsx}' > /tmp/expo_image_imports.txt || true
# Find onLoadStart/onLoadEnd/onLoad/onError usages in non-snapshot code
rg -n "onLoadStart\s*=|onLoadEnd\s*=|onLoad\s*=|onError\s*=" app --glob='*.{ts,tsx}' > /tmp/image_callbacks.txt || true
# Show a small subset of matches that mention expo-image Image usage patterns
# (filter by nearby presence of "from 'expo-image'" within same file)
python3 - <<'PY'
import re, pathlib
imports=set()
for line in open('/tmp/expo_image_imports.txt','r',encoding='utf-8'):
# format: path:line:content
path=line.split(':',1)[0]
imports.add(path)
matches=[]
for line in open('/tmp/image_callbacks.txt','r',encoding='utf-8'):
path=line.split(':',1)[0]
if path in imports:
matches.append(line.strip())
print("Matches in actual code files that import { Image } from 'expo-image':")
for m in matches[:80]:
print(m)
print("Total:", len(matches))
PYRepository: RocketChat/Rocket.Chat.ReactNative
Length of output: 257
🌐 Web query:
expo-image Image props onLoadStart onLoadEnd onError supported
💡 Result:
The expo-image component supports the following event props for tracking image loading status: onLoadStart: Called when the image starts to load [1][2]. onLoadEnd: Called when the image load completes, whether it succeeds or fails [1][2]. onError: Called when an image fetching error occurs [1][2]. It receives an event object of type ImageErrorEventData [1][2]. These props are supported across the platforms that expo-image covers, which include Android, iOS, tvOS, and Web [3][4]. Note that in addition to the standard component prop, there is also an onError handler used within the useImage hook, which provides the error and a retry function [3][4].
Citations:
- 1: https://github.com/expo/expo/blob/68661df5/packages/expo-image/build/Image.types.d.ts
- 2: https://github.com/expo/expo/blob/5cbac55c/packages/expo-image/build/Image.types.d.ts
- 3: https://docs.expo.dev/versions/v53.0.0/sdk/image/
- 4: https://docs.expo.dev/versions/latest/sdk/image/
Show skeleton while the avatar image is loading and on error
app/views/WorkspaceView/ServerAvatar.tsx renders <Image /> whenever image is truthy, with the skeleton placeholder only shown when image is falsy—so slow/failed remote loads can leave the component blank instead of displaying the skeleton.
Suggested fix
+const ServerAvatar = React.memo(({ url, image }: IServerAvatar): React.JSX.Element => {
+ const [isImageLoading, setIsImageLoading] = React.useState<boolean>(Boolean(image));
+ const [hasImageError, setHasImageError] = React.useState<boolean>(false);
+
+ React.useEffect(() => {
+ setIsImageLoading(Boolean(image));
+ setHasImageError(false);
+ }, [url, image]);
+
return (
<View style={styles.container}>
- {image ? (
- <Image style={[styles.image, { borderColor: colors.strokeLight }]} source={{ uri: `${url}/${image}` }} />
- ) : (
+ {(!image || isImageLoading || hasImageError) ? (
<SkeletonPlaceholder backgroundColor={colors.surfaceNeutral} borderRadius={BORDER_RADIUS}>
<SkeletonPlaceholder.Item width={SIZE} height={SIZE} />
</SkeletonPlaceholder>
+ ) : null}
+ {image ? (
+ <Image
+ style={[styles.image, { borderColor: colors.strokeLight }]}
+ source={{ uri: `${url}/${image}` }}
+ onLoadStart={() => setIsImageLoading(true)}
+ onLoadEnd={() => setIsImageLoading(false)}
+ onError={() => {
+ setIsImageLoading(false);
+ setHasImageError(true);
+ }}
+ />
)}
</View>
);
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/views/WorkspaceView/ServerAvatar.tsx` around lines 38 - 44, ServerAvatar
currently renders Image whenever image is truthy so the SkeletonPlaceholder only
shows when image is falsy; change ServerAvatar to track loading state (e.g.,
isLoading/isError) and render the SkeletonPlaceholder while isLoading or isError
is true, then render the Image when loaded successfully. Add Image onLoad to
clear isLoading and onError to set isError (and stop loading), ensure the Image
source uses `${url}/${image}` only when image exists, and keep existing styles
(styles.image, colors.strokeLight, SIZE, BORDER_RADIUS) so the skeleton displays
during slow or failed network loads.
Proposed changes
This PR introduces a skeleton loading state to the
ServerAvatarcomponent in theWorkspaceView. Previously, when the server avatar image was loading or unavailable, the component would render empty space, which resulted in a subpar user experience and left a// TODO: missing skeletoncomment in the codebase.Changes included:
Issue(s)
N/A (Resolves an existing codebase TODO)
How to test or reproduce
WorkspaceView(the server selection/login screen).ServerAvatarcomponent before the image fully loads.pnpm storybook:startand navigating toWorkspaceView/ServerAvatar.Types of changes
Checklist
Further comments
This PR also increases the test coverage for the
ServerAvatarcomponent by adding snapshot tests and Storybook stories, strictly following the established patterns in the project (usinggenerateSnapshotsandThemeContext.Provider).Summary by CodeRabbit
New Features
Tests