mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 18:30:15 +01:00
refactor: streamline UserAvatar component by optimizing image loading and error handling
This commit is contained in:
parent
39d83b705b
commit
c63f2a634c
1 changed files with 49 additions and 55 deletions
|
|
@ -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 { UserIcon, useAvatar } from '@librechat/client';
|
||||||
import type { TUser } from 'librechat-data-provider';
|
import type { TUser } from 'librechat-data-provider';
|
||||||
import type { IconProps } from '~/common';
|
import type { IconProps } from '~/common';
|
||||||
|
|
@ -15,20 +15,10 @@ type UserAvatarProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAvatarProps) => {
|
/**
|
||||||
const [imageError, setImageError] = useState(false);
|
* Default avatar component - memoized outside to prevent recreation on every render
|
||||||
const [imageLoaded, setImageLoaded] = useState(false);
|
*/
|
||||||
|
const DefaultAvatar = memo(() => (
|
||||||
const handleImageError = () => {
|
|
||||||
setImageError(true);
|
|
||||||
setImageLoaded(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageLoad = () => {
|
|
||||||
setImageLoaded(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDefaultAvatar = () => (
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgb(121, 137, 255)',
|
backgroundColor: 'rgb(121, 137, 255)',
|
||||||
|
|
@ -40,19 +30,34 @@ const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAva
|
||||||
>
|
>
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
</div>
|
</div>
|
||||||
);
|
));
|
||||||
|
|
||||||
|
DefaultAvatar.displayName = 'DefaultAvatar';
|
||||||
|
|
||||||
|
const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAvatarProps) => {
|
||||||
|
const [imageError, setImageError] = useState(false);
|
||||||
|
const imageLoadedRef = useRef(false);
|
||||||
|
const currentSrcRef = useRef('');
|
||||||
|
|
||||||
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]);
|
const imageSrc = useMemo(() => (user?.avatar ?? '') || avatarSrc, [user?.avatar, avatarSrc]);
|
||||||
|
|
||||||
/** Check if we're using a custom avatar (not initials) */
|
/** Reset loaded state if image source changes */
|
||||||
const isCustomAvatar = useMemo(() => !!(user?.avatar && user.avatar !== ''), [user?.avatar]);
|
if (currentSrcRef.current !== imageSrc) {
|
||||||
// /** Only show default while loading for custom avatars, not for initials */
|
imageLoadedRef.current = false;
|
||||||
// const showDefaultWhileLoading = useMemo(
|
currentSrcRef.current = imageSrc;
|
||||||
// () => isCustomAvatar && !imageLoaded && showImage,
|
}
|
||||||
// [isCustomAvatar, imageLoaded, showImage],
|
|
||||||
// );
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -63,27 +68,16 @@ const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAva
|
||||||
}}
|
}}
|
||||||
className={cn('relative flex items-center justify-center', className ?? '')}
|
className={cn('relative flex items-center justify-center', className ?? '')}
|
||||||
>
|
>
|
||||||
{/* Always render default avatar as background */}
|
{!showImage ? (
|
||||||
{!showImage && renderDefaultAvatar()}
|
<DefaultAvatar />
|
||||||
{showImage && (
|
) : (
|
||||||
<>
|
|
||||||
{/* Show default avatar behind the image while loading custom avatars */}
|
|
||||||
{isCustomAvatar && !imageLoaded && (
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
|
||||||
{renderDefaultAvatar()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<img
|
<img
|
||||||
className={cn(
|
className="rounded-full"
|
||||||
'relative z-10 rounded-full',
|
|
||||||
isCustomAvatar && !imageLoaded ? 'opacity-0' : 'opacity-100',
|
|
||||||
)}
|
|
||||||
src={imageSrc}
|
src={imageSrc}
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue