From c63f2a634c01cc23f3d8c897a7dcdb6d887d60a1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 26 Oct 2025 07:54:32 -0400 Subject: [PATCH] refactor: streamline UserAvatar component by optimizing image loading and error handling --- client/src/components/Endpoints/Icon.tsx | 104 +++++++++++------------ 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/client/src/components/Endpoints/Icon.tsx b/client/src/components/Endpoints/Icon.tsx index df36d6a35e..482915db38 100644 --- a/client/src/components/Endpoints/Icon.tsx +++ b/client/src/components/Endpoints/Icon.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState, useMemo } from 'react'; +import React, { memo, useState, useMemo, useRef, useCallback } from 'react'; import { UserIcon, useAvatar } from '@librechat/client'; import type { TUser } from 'librechat-data-provider'; import type { IconProps } from '~/common'; @@ -15,44 +15,49 @@ type UserAvatarProps = { className?: string; }; +/** + * Default avatar component - memoized outside to prevent recreation on every render + */ +const DefaultAvatar = memo(() => ( +
+ +
+)); + +DefaultAvatar.displayName = 'DefaultAvatar'; + const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAvatarProps) => { const [imageError, setImageError] = useState(false); - const [imageLoaded, setImageLoaded] = useState(false); + const imageLoadedRef = useRef(false); + const currentSrcRef = useRef(''); - const handleImageError = () => { - setImageError(true); - setImageLoaded(false); - }; - - const handleImageLoad = () => { - setImageLoaded(true); - }; - - const renderDefaultAvatar = () => ( -
- -
- ); - - const hasAvatar = useMemo(() => (user?.avatar ?? '') || avatarSrc, [user?.avatar, avatarSrc]); - const showImage = useMemo(() => hasAvatar && !imageError, [hasAvatar, imageError]); const imageSrc = useMemo(() => (user?.avatar ?? '') || avatarSrc, [user?.avatar, avatarSrc]); - /** Check if we're using a custom avatar (not initials) */ - const isCustomAvatar = useMemo(() => !!(user?.avatar && user.avatar !== ''), [user?.avatar]); - // /** Only show default while loading for custom avatars, not for initials */ - // const showDefaultWhileLoading = useMemo( - // () => isCustomAvatar && !imageLoaded && showImage, - // [isCustomAvatar, imageLoaded, showImage], - // ); + /** Reset loaded state if image source changes */ + if (currentSrcRef.current !== imageSrc) { + imageLoadedRef.current = false; + currentSrcRef.current = imageSrc; + } + + const handleImageError = useCallback(() => { + setImageError(true); + imageLoadedRef.current = false; + }, []); + + const handleImageLoad = useCallback(() => { + imageLoadedRef.current = true; + }, []); + + const hasAvatar = useMemo(() => imageSrc !== '', [imageSrc]); + const showImage = useMemo(() => hasAvatar && !imageError, [hasAvatar, imageError]); return (
- {/* Always render default avatar as background */} - {!showImage && renderDefaultAvatar()} - {showImage && ( - <> - {/* Show default avatar behind the image while loading custom avatars */} - {isCustomAvatar && !imageLoaded && ( -
- {renderDefaultAvatar()} -
- )} - avatar - + {!showImage ? ( + + ) : ( + avatar )}
);