User
User Service
Reactive user state management with Svelte 5 runes and Firebase Authentication sync
User Service
The firekitUser
service provides reactive user state management for your Svelte 5 application, automatically syncing with Firebase Authentication and providing clean reactive state.
Overview
The user service provides:
- Reactive user state using Svelte 5 runes
- Automatic synchronization with Firebase Auth
- Email verification status tracking
- Loading and error states
- Real-time user updates
- Type-safe user data
Quick Start
<script>
import { firekitUser } from 'svelte-firekit';
// Reactive user state using Svelte 5 runes
const user = $derived(firekitUser.user);
const isAuthenticated = $derived(firekitUser.isAuthenticated);
const isLoading = $derived(firekitUser.loading);
const isEmailVerified = $derived(firekitUser.isEmailVerified);
const error = $derived(firekitUser.error);
// ✅ Use $derived for computed values
const displayName = $derived(user?.displayName || user?.email || 'Anonymous User');
const avatarUrl = $derived(user?.photoURL || '/default-avatar.png');
// ✅ Use $effect only for side effects
$effect(() => {
if (isAuthenticated && user) {
console.log('User authenticated:', displayName);
}
});
</script>
{#if isLoading}
<div>Loading user...</div>
{:else if error}
<div>Error: {error.message}</div>
{:else if isAuthenticated}
<div>
<img src={avatarUrl} alt={displayName} />
<h1>Welcome, {displayName}!</h1>
{#if isEmailVerified}
<p class="text-green-500">✓ Email verified</p>
{:else}
<p class="text-yellow-500">⚠ Email not verified</p>
{/if}
</div>
{:else}
<div>Please sign in</div>
{/if}
Reactive State
Basic User State
<script>
import { firekitUser } from 'svelte-firekit';
// Core reactive user state
const user = $derived(firekitUser.user);
const isAuthenticated = $derived(firekitUser.isAuthenticated);
const isLoading = $derived(firekitUser.loading);
const error = $derived(firekitUser.error);
// User properties (all reactive)
const userId = $derived(user?.uid);
const email = $derived(user?.email);
const displayName = $derived(user?.displayName);
const photoURL = $derived(user?.photoURL);
const phoneNumber = $derived(user?.phoneNumber);
const isAnonymous = $derived(user?.isAnonymous || false);
</script>
Email Verification State
<script>
import { firekitUser } from 'svelte-firekit';
const isEmailVerified = $derived(firekitUser.isEmailVerified);
const user = $derived(firekitUser.user);
// Computed verification status
const verificationStatus = $derived(
!user ? 'unknown' :
user.isAnonymous ? 'not-required' :
isEmailVerified ? 'verified' : 'pending'
);
const verificationMessage = $derived({
'unknown': 'User not loaded',
'not-required': 'Email verification not required',
'verified': 'Email verified ✓',
'pending': 'Please verify your email ⚠'
}[verificationStatus]);
</script>
<div class="verification-status">
<p>{verificationMessage}</p>
</div>
User Metadata
<script>
import { firekitUser } from 'svelte-firekit';
const user = $derived(firekitUser.user);
const metadata = $derived(user?.metadata);
// Formatted metadata
const createdAt = $derived(
metadata?.creationTime
? new Date(metadata.creationTime).toLocaleDateString()
: null
);
const lastSignIn = $derived(
metadata?.lastSignInTime
? new Date(metadata.lastSignInTime).toLocaleDateString()
: null
);
</script>
{#if metadata}
<div class="user-metadata">
<p>Account created: {createdAt}</p>
<p>Last sign in: {lastSignIn}</p>
</div>
{/if}
State Management
Authentication State Changes
<script>
import { firekitUser } from 'svelte-firekit';
import { goto } from '$app/navigation';
const isAuthenticated = $derived(firekitUser.isAuthenticated);
const user = $derived(firekitUser.user);
// ✅ Use $effect for navigation side effects
$effect(() => {
if (isAuthenticated) {
console.log('User authenticated, loading dashboard...');
// Side effect: navigation, analytics, etc.
} else {
console.log('User not authenticated');
goto('/login');
}
});
</script>
Loading States
<script>
import { firekitUser } from 'svelte-firekit';
const isLoading = $derived(firekitUser.loading);
const isAuthenticated = $derived(firekitUser.isAuthenticated);
// Computed loading state
const showSpinner = $derived(isLoading);
const showContent = $derived(!isLoading && isAuthenticated);
</script>
{#if showSpinner}
<div class="loading-spinner">Loading...</div>
{:else if showContent}
<!-- User content -->
<slot />
{:else}
<!-- Not authenticated content -->
<div>Please sign in</div>
{/if}
Error Handling
<script>
import { firekitUser } from 'svelte-firekit';
const error = $derived(firekitUser.error);
const hasError = $derived(!!error);
// Computed error message
const errorMessage = $derived(
error ? `Authentication error: ${error.message}` : null
);
// ✅ Use $effect for error tracking side effects
$effect(() => {
if (error) {
console.error('User service error:', error);
// Side effect: send to error tracking service
}
});
</script>
{#if hasError}
<div class="error-message">
<p>{errorMessage}</p>
<button onclick={() => window.location.reload()}>Retry</button>
</div>
{/if}
Svelte Component Integration
User Profile Component
<script>
import { firekitUser } from 'svelte-firekit';
import { Avatar, AvatarImage, AvatarFallback } from '$lib/components/ui/avatar';
import { Badge } from '$lib/components/ui/badge';
const user = $derived(firekitUser.user);
const isAuthenticated = $derived(firekitUser.isAuthenticated);
const isEmailVerified = $derived(firekitUser.isEmailVerified);
// Computed values using $derived
const displayName = $derived(user?.displayName || 'User');
const userInitials = $derived(
user?.displayName
? user.displayName.split(' ').map(n => n[0]).join('').toUpperCase()
: user?.email?.[0]?.toUpperCase() || '?'
);
const verificationBadge = $derived(
isEmailVerified
? { variant: 'default', text: 'Verified' }
: { variant: 'secondary', text: 'Unverified' }
);
</script>
{#if isAuthenticated && user}
<div class="user-profile-card">
<div class="profile-header">
<Avatar>
<AvatarImage src={user.photoURL} alt={displayName} />
<AvatarFallback>{userInitials}</AvatarFallback>
</Avatar>
<div class="profile-info">
<h2>{displayName}</h2>
<p>{user.email}</p>
<Badge variant={verificationBadge.variant}>
{verificationBadge.text}
</Badge>
</div>
</div>
</div>
{/if}
User Menu Component
<script>
import { firekitUser, firekitAuth } from 'svelte-firekit';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '$lib/components/ui/dropdown-menu';
import { Avatar, AvatarImage, AvatarFallback } from '$lib/components/ui/avatar';
import { Button } from '$lib/components/ui/button';
const user = $derived(firekitUser.user);
const isAuthenticated = $derived(firekitUser.isAuthenticated);
// Computed values
const userInitials = $derived(
user?.displayName
? user.displayName.split(' ').map(n => n[0]).join('').toUpperCase()
: user?.email?.[0]?.toUpperCase() || '?'
);
async function handleSignOut() {
try {
await firekitAuth.signOut();
} catch (error) {
console.error('Sign out failed:', error);
}
}
</script>
{#if isAuthenticated && user}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" class="relative h-8 w-8 rounded-full">
<Avatar class="h-8 w-8">
<AvatarImage src={user.photoURL} alt={user.displayName} />
<AvatarFallback>{userInitials}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56" align="end">
<div class="flex items-center justify-start gap-2 p-2">
<div class="flex flex-col space-y-1 leading-none">
{#if user.displayName}
<p class="font-medium">{user.displayName}</p>
{/if}
<p class="w-[200px] truncate text-sm text-muted-foreground">
{user.email}
</p>
</div>
</div>
<DropdownMenuItem onclick={() => window.location.href = '/profile'}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onclick={() => window.location.href = '/settings'}>
Settings
</DropdownMenuItem>
<DropdownMenuItem onclick={handleSignOut}>
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{:else}
<Button onclick={() => window.location.href = '/login'}>Sign In</Button>
{/if}
Type Definitions
interface User {
uid: string;
email: string | null;
displayName: string | null;
photoURL: string | null;
phoneNumber: string | null;
emailVerified: boolean;
isAnonymous: boolean;
metadata: UserMetadata;
providerData: UserInfo[];
}
interface UserMetadata {
creationTime: string;
lastSignInTime: string;
}
interface UserInfo {
uid: string;
displayName: string | null;
email: string | null;
photoURL: string | null;
providerId: string;
}
Best Practices
Reactivity Patterns
✅ Good Patterns
<script>
const user = $derived(firekitUser.user);
// ✅ Use $derived for all computations
const isAdmin = $derived(user?.email?.includes('admin') || false);
const canEdit = $derived(isAdmin && user?.emailVerified);
const userName = $derived(user?.displayName || user?.email || 'Anonymous');
// ✅ Use $effect only for side effects
$effect(() => {
if (canEdit) {
document.title = `Admin Panel - ${userName}`;
}
});
</script>
❌ Avoid These Patterns
<script>
const user = $derived(firekitUser.user);
let isAdmin = $state(false);
let userName = $state('');
// ❌ Don't use $effect for simple computations
$effect(() => {
isAdmin = user?.email?.includes('admin') || false;
userName = user?.displayName || user?.email || 'Anonymous';
});
</script>
Performance
- Use
$derived
for all computed values - automatic dependency tracking - Minimize
$effect
usage - only for actual side effects - Avoid deep reactive chains - keep derivations shallow when possible
- Use optional chaining -
user?.property
instead of checking existence
Error Handling
<script>
const error = $derived(firekitUser.error);
const user = $derived(firekitUser.user);
const isLoading = $derived(firekitUser.loading);
// Comprehensive loading state
const loadingState = $derived(
error ? 'error' :
isLoading ? 'loading' :
user ? 'loaded' : 'no-user'
);
</script>
{#if loadingState === 'loading'}
<LoadingSpinner />
{:else if loadingState === 'error'}
<ErrorMessage {error} />
{:else if loadingState === 'loaded'}
<UserContent {user} />
{:else}
<SignInPrompt />
{/if}
API Reference
Reactive Properties
All properties are reactive and should be accessed using $derived()
:
user
- Current user object (User | null)isAuthenticated
- Authentication status (boolean)isLoading
- Loading state (boolean)isEmailVerified
- Email verification status (boolean)error
- Current error (Error | null)
Usage Examples
// ✅ Correct usage with $derived
const user = $derived(firekitUser.user);
const isAuth = $derived(firekitUser.isAuthenticated);
// ❌ Incorrect - accessing directly
const user = firekitUser.user; // Won't be reactive
Integration with Firebase Auth
The service automatically:
- Syncs with Firebase Authentication state changes
- Updates when user profile changes
- Handles authentication errors
- Manages loading states during auth operations
- Tracks email verification status changes