Frontend State Management
Zustand-powered state management for loading states, global banners, and notification service integration.
Frontend State Management
The SLMS frontend uses Zustand for global state management alongside NextAuth for session/auth state and the built-in React component state for local UI concerns.
State Architecture
┌──────────────────────────────────────────┐
│ NextAuth Session │ ← User identity, role, accessToken
├──────────────────────────────────────────┤
│ Zustand Stores │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ useBannerStore │ │ useLoadingStore│ │
│ │ (system alerts)│ │ (request state)│ │
│ └────────────────┘ └────────────────┘ │
├──────────────────────────────────────────┤
│ Notification Service │ ← Wraps Sonner + Banner Store
├──────────────────────────────────────────┤
│ React Component State │ ← Local UI (form values, modals)
└──────────────────────────────────────────┘Zustand Stores
useBannerStore
File: src/stores/useBannerStore.ts
Manages a single global alert banner (only one visible at a time).
interface BannerState {
id: string;
message: string;
severity: 'info' | 'warning' | 'error' | 'critical';
actionText?: string;
onAction?: () => void;
dismissible: boolean;
}
interface BannerStore {
activeBanner: BannerState | null;
showBanner: (banner) => void;
hideBanner: () => void;
}Triggers:
- Offline detection hook (
useOfflineDetection) - Network errors in Axios interceptor
- Error Boundary component
useLoadingStore
File: src/stores/useLoadingStore.ts
Manages both global page-level loading and scoped, key-based request loading states.
interface LoadingStore {
isGlobalLoading: boolean;
setGlobalLoading: (isLoading: boolean) => void;
activeRequests: Set<string>;
startRequest: (key: string) => void;
endRequest: (key: string) => void;
isLoading: (key: string) => boolean;
}Usage Example:
const { startRequest, endRequest, isLoading } = useLoadingStore();
const handleSave = async () => {
startRequest('save-book');
try {
await api.post('/books', data);
} finally {
endRequest('save-book');
}
};
// In the template:
<Button disabled={isLoading('save-book')}>
{isLoading('save-book') ? 'Saving...' : 'Save'}
</Button>NextAuth Session
Session state is managed entirely by NextAuth and accessed via the useSession() hook or the auth() server function:
// Client component
const { data: session } = useSession();
const role = session?.user?.role;
const accessToken = (session as any)?.accessToken;
// Server component / middleware
const session = await auth();Session Shape
{
user: {
id: string;
name: string;
email: string;
role: string; // "ROLE_ADMIN" | "ROLE_LIBRARIAN" | etc.
rollNumber: string;
contactNumber: string;
},
accessToken: string;
error?: string; // "RefreshAccessTokenError" if refresh failed
}Notification Service (Non-Store)
The NotificationService class (src/services/notification.service.ts) is not a Zustand store. It's a singleton class that imperatively triggers toasts and banners:
import { notification } from '@/services/notification.service';
notification.success('Saved'); // Sonner toast
notification.error('Failed'); // Sonner toast
notification.banner('Offline', 'warning'); // Updates useBannerStore
notification.clearBanner(); // Clears useBannerStore
const ok = await notification.confirm('Delete?', 'Cannot undo.'); // Promise<boolean>This approach keeps components simple — they call notification.success() without needing to import stores or manage toast state.
Theme State
Provider: src/components/theme-provider.tsx
Uses next-themes with three modes:
lightdarksystem(default)
The theme toggle component (theme-toggle.tsx) switches between modes. The ThemeProvider wraps the entire app in layout.tsx.