A React component that displays the current authenticated user’s avatar with automatic loading states and graceful fallbacks.
Installation
npx shadcn@latest add https://convex-ui.vercel.app/r/current-user-avatar-react
This installs:
CurrentUserAvatar component
- Custom hooks for user data
- User query functions
- Convex client setup
What’s Included
Avatar Component
Beautiful avatar with image fallback to initials
Loading States
Skeleton loader while fetching user data
Custom Hooks
Reusable hooks for user name and image
Type Safe
Full TypeScript support with proper types
Component
CurrentUserAvatar
Displays the current user’s avatar with automatic loading and fallback states.
import { CurrentUserAvatar } from "@/components/current-user-avatar";
function Header() {
return (
<div className="flex items-center gap-2">
<span>Welcome back!</span>
<CurrentUserAvatar size="md" />
</div>
);
}
Props
size
'sm' | 'md' | 'lg'
default:"md"
Size of the avatar:
sm: 32px (h-8 w-8)
md: 40px (h-10 w-10)
lg: 48px (h-12 w-12)
Additional CSS classes to apply to the avatar
Whether to show fallback avatar when user is not authenticated. If false, returns null when no user.
Usage Examples
import { CurrentUserAvatar } from '@/components/current-user-avatar';
function AvatarSizes() {
return (
<div className="flex items-center gap-4">
<CurrentUserAvatar size="sm" />
<CurrentUserAvatar size="md" />
<CurrentUserAvatar size="lg" />
</div>
);
}
Custom Hooks
The component includes custom hooks for accessing user data:
useCurrentUserName
Get the current user’s name.
import { useCurrentUserName } from '@/hooks/use-current-user-name';
function Greeting() {
const name = useCurrentUserName();
// undefined = loading, null = not authenticated
if (name === undefined) {
return <span>Loading...</span>;
}
if (name === null) {
return <span>Welcome, Guest!</span>;
}
return <span>Welcome, {name}!</span>;
}
Returns: string | null | undefined
undefined - Still loading user data
null - User not authenticated
string - User’s name
useCurrentUserImage
Get the current user’s profile image URL.
import { useCurrentUserImage } from '@/hooks/use-current-user-image';
function CustomAvatar() {
const imageUrl = useCurrentUserImage();
if (imageUrl === undefined) {
return <div className="skeleton" />;
}
return (
<img
src={imageUrl || '/default-avatar.png'}
alt="User avatar"
className="w-10 h-10 rounded-full"
/>
);
}
Returns: string | null | undefined
undefined - Still loading user data
null - User not authenticated or no image set
string - User’s profile image URL
Component Source
The avatar component uses the custom hooks and shadcn/ui Avatar:
components/current-user-avatar.tsx
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { useCurrentUserImage } from "../hooks/use-current-user-image";
import { useCurrentUserName } from "../hooks/use-current-user-name";
interface CurrentUserAvatarProps {
size?: "sm" | "md" | "lg";
className?: string;
showFallback?: boolean;
}
const sizeClasses = {
sm: "h-8 w-8 text-xs",
md: "h-10 w-10 text-sm",
lg: "h-12 w-12 text-base",
};
function getInitials(name: string | null): string {
if (!name) return "?";
return name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);
}
export function CurrentUserAvatar({
size = "md",
className,
showFallback = true,
}: CurrentUserAvatarProps) {
const image = useCurrentUserImage();
const name = useCurrentUserName();
// Loading state
if (image === undefined || name === undefined) {
return (
<Skeleton className={cn("rounded-full", sizeClasses[size], className)} />
);
}
// Not authenticated
if (!showFallback && !image && !name) {
return null;
}
return (
<Avatar className={cn(sizeClasses[size], className)}>
<AvatarImage src={image ?? undefined} alt={name ?? "User"} />
<AvatarFallback>{getInitials(name)}</AvatarFallback>
</Avatar>
);
}
Backend Function
The component uses the api.users.current query:
export const current = query({
args: {},
returns: v.union(userValidator, v.null()),
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
return null;
}
return await ctx.db.get(userId);
},
});
Returns:
{
_id: Id<"users">,
_creationTime: number,
name?: string,
email?: string,
image?: string,
emailVerificationTime?: number,
isAnonymous?: boolean
} | null
States
The component handles three states automatically:
Loading State
Shows a skeleton loader while fetching user data:
<Skeleton className="h-10 w-10 rounded-full" />
Authenticated State
Shows user’s avatar image or fallback to initials:
<Avatar>
<AvatarImage src={user.image} />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
Unauthenticated State
Either shows a ”?” fallback or returns null based on showFallback prop:
// showFallback={true} (default)
<Avatar>
<AvatarFallback>?</AvatarFallback>
</Avatar>
// showFallback={false}
return null;
Styling
Custom Sizes
Override size with className:
<CurrentUserAvatar
size="md"
className="h-16 w-16" // Override to 64px
/>
Custom Border
<CurrentUserAvatar
size="lg"
className="ring-2 ring-primary ring-offset-2"
/>
Custom Background
<CurrentUserAvatar
size="md"
className="bg-gradient-to-br from-purple-500 to-pink-500"
/>
Integration with Auth
Requires Convex Auth to be configured. Works with:
- Password-based authentication
- Social authentication (GitHub, Google)
- Any Convex Auth provider
See Password Auth or Social Auth for setup.
Accessibility
- Uses semantic HTML with
<img> alt text
- Keyboard accessible when used in interactive elements
- Screen reader friendly with proper labels
The component is optimized for performance:
- Uses Convex’s reactive queries (only re-renders on user data changes)
- Minimal re-renders with proper hook memoization
- Lazy loads user data only when component is mounted
Next Steps
Password Auth
Set up user authentication
Social Auth
Add GitHub and Google sign-in
Realtime Avatar Stack
Show multiple users in a room